JavaScript设计模式-创建型-工厂模式

2021-05-08 JavaScript设计模式 阅读 616 次

1. 什么是工厂模式

工厂模式主要可以分为三大类:

  • 简单工厂模式(Simple Factory)
  • 工厂方法模式(Factory Method)
  • 抽象工厂模式(Abstract Factory)

JavaScript一直将abstract作为保留字而没有去实现它,JavaScript 开发者可能对抽象理解会比较模糊,如果不能很好地理解抽象,就很难理解工厂模式中的三种方法的异同。

先理解下什么是抽象

抽象:将复杂事物的一个或多个共有特征抽取出来的思维过程。

我们先以一个场景去简单的讲述一下抽象和工厂的概念。

我想买JavaScript设计模式这本书,JavaScript设计模式可以被看作是一个实例对象。

书店可以看作是一个工厂,可以认为它是一个函数,书店里有各式各样的书籍(各种实例对象),那我该如何得到我想要的书(实例对象)呢?

我会告诉书店,我要书名是JavaScript设计模式,作者是某某的书。

如果我告诉书店“我想要一本技术书籍”,技术书籍分很多种类,书店无法给到我具体的哪一本。我向书店发出请求“我想要一本技术书籍”是一大类书籍的共有特征而不是具体书籍,这种将复杂事物的一个或多个共有特征抽取出来的思维过程就是抽象

1.1 简单工厂模式

简单工厂模式又叫静态工厂模式,由一个工厂对象决定创建某一种产品对象类的实例。主要用来创建同一类对象,通常这些类拥有相同的父类。

假设现在有 3 款车,BenzAudiBMW,他们都继承自父类 Car,并且重写了父类方法 drive

var Car = function () {}
Car.prototype.drive = function () {
	console.log('Car drive')
}

var Benz = function () {}
Benz.prototype = Object.create(Car.prototype)
Benz.prototype.constructor = Benz
Benz.prototype.drive = function () {
	console.log('Benz drive')
}

var Audi = function () {}
Audi.prototype = Object.create(Car.prototype)
Audi.prototype.constructor = Audi
Audi.prototype.drive = function () {
	console.log('Audi drive')
}

var BMW = function () {}
BMW.prototype = Object.create(Car.prototype)
BMW.prototype.constructor = BMW
BMW.prototype.drive = function () {
	console.log('BMW drive')
}

定义了一个父类Car, drive方法。

BenzAudiBMW 继承自共同的父类 Car,并重写了drive方法。

实例化这 3 款车的时候,就需要如下调用:

var benz = new Benz()
var audi = new Audi()
var bmw = new BMW()

benz.drive()			// Benz drive
audi.drive()			// Audi drive
bmw.drive()			// BMW drive

当我们需要使用以上类的时候,使用者必须要知道具体的产品类名,从而进行实例化才可以得到对象。

这种写法就很繁琐,这时候就用到我们的简单工厂了,提供一个工厂类:

var SimpleFactory = function () {}

SimpleFactory.getCar = function (name) {
	switch (name) {
		case 'benz':
			return new Benz()
		case 'audi':
			return new Audi()
		case 'bmw':
			return new BMW()
	}
}

简单工厂类 SimpleFactory 提供一个静态方法 getCar,我们再实例化 3 款车的时候,就变成下面这样了:

var benz = SimpleFactory.getCar('benz')
var audi = SimpleFactory.getCar('audi')
var bmw = SimpleFactory.getCar('bmw')

benz.drive()			// Benz drive
audi.drive()			// Audi drive
bmw.drive()			// BMW drive

上面代码使用简单工厂后,发现代码量反而增加了,这种简单工厂写法不是多此一举吗?有什么优势呢?

设计模式并不是为了减少代码行数而出现的,它的出现是使我们的代码更好扩展,更好维护,更方便。

使用简单工厂后有什么好处呢?

简单工厂,用户不需要知道具体产品的类名,只需要知道对应的参数即可,对于一些复杂的类名,可以减少用户的记忆量,同时用户无需了解这些对象是如何创建及组织的,有利于整个软件体系结构的优化。

所以,使用简单工厂,是将类的实例化交给工厂函数去做,对外提供统一的方法。我们要养成一个习惯,在代码中 new 是一个需要慎重考虑的操作,new 出现的次数越多,代码的耦合性就越强,可维护性就越差,简单工厂,就是在上面做了一层抽象,将 new 的操作封装了起来,向外提供静态方法供用户调用,这样就将耦合集中到了工厂函数中,而不是暴露在代码的各个位置。

简单工厂也有着它的缺点,当我们想新增一类车 Toyota ,需要在简单工厂类 SimpleFactory 中再新增一个 case,添加 Toyota 的实例化过程。每新增一类车,就要修改简单工厂类,这样做其实违背了设计原则中的开闭原则:对扩展开放,对修改封闭,同时如果每类车在实例化之前需要做一些处理逻辑的话,SimpleFactory 会变的越来越复杂。

所以简单工厂适用于产品类比较少并且不会频繁增加的情况,那么有什么方法能解决简单工厂存在的问题呢?

1.2 工厂方法模式

工厂方法模式是简单工厂的进一步优化,我们不再提供一个统一的工厂类来创建所有的对象,而是针对不同的对象提供不同的工厂,也就是说每个对象都有一个与之对应的工厂。

工厂方法模式又称为工厂模式,它的实质是“定义一个创建对象的接口,但让实现这个接口的类来决定实例化哪个类,工厂方法让类的实例化推迟到子类中执行”。

看概念很难理解,针对上面简单工厂的例子来做优化,举例说明。

首先还是先定义父类Car,提供diver方法:

var Car = function () {}
Car.prototype.drive = function () {
	console.log('Car drive')
}

所有产品的车都继承父类Car, 并且重新drive

var Benz = function () {}
Benz.prototype = Object.create(Car.prototype)
Benz.prototype.constructor = Benz
Benz.prototype.drive = function () {
	console.log('Benz drive')
}

var Audi = function () {}
Audi.prototype = Object.create(Car.prototype)
Audi.prototype.constructor = Audi
Audi.prototype.drive = function () {
	console.log('Audi drive')
}

var BMW = function () {}
BMW.prototype = Object.create(Car.prototype)
BMW.prototype.constructor = BMW
BMW.prototype.drive = function () {
	console.log('BMW drive')
}

然后按照定义,我们提供一个创建对象的接口:

var IFactory = function () {}
IFactory.prototype.getCar = function () {
	throw new Error('不允许直接调用抽象方法,请继承子类重写该方法')
}

每款车都提供一个工厂类,实现自上述接口:

var BenzFactory = function () {}
BenzFactory.prototype = Object.create(IFactory.prototype)
BenzFactory.prototype.constructor = BenzFactory
BenzFactory.prototype.getCar = function () {
	return new Benz()
}

var AudiFactory = function () {}
AudiFactory.prototype = Object.create(IFactory.prototype)
AudiFactory.prototype.constructor = AudiFactory
AudiFactory.prototype.getCar = function () {
	return new Audi()
}

var BMWFactory = function () {}
BMWFactory.prototype = Object.create(IFactory.prototype)
BMWFactory.prototype.constructor = BMWFactory
BMWFactory.prototype.getCar = function () {
	return new BMW()
}

这样当我们需要实例化每款车的时候,就按如下操作:

var benzFactory = new BenzFactory()
var benz = benzFactory.getCar()

var audiFactory = new AudiFactory()
var audi = audiFactory.getCar()

var bmwFactory = new BMWFactory()
var bmw = bmwFactory.getCar()

benz.drive()			// Benz drive
audi.drive()			// Audi drive
bmw.drive()			// BMW drive

现在回头来对比下简单工厂的实例化过程:

var benz = SimpleFactory.getCar('benz')
var audi = SimpleFactory.getCar('audi')
var bmw = SimpleFactory.getCar('bmw')

benz.drive()			// Benz drive
audi.drive()			// Audi drive
bmw.drive()			// BMW drive

我们用一张图来描述一下工厂方法模式:

JavaScript设计模式-创建型-工厂模式-黄继鹏博客

我们看到,同样是实例化对象,工厂方法模式反而变得更复杂,根据上图,来总结下厂方法模式的步骤:

  1. 定义汽车产品父类Car
  2. 定义子类实现父类,并重写父类方法 -- BenzCar、AudiCar、BMWCar
  3. 定义抽象接口IFactory,以及抽象方法 getCar
  4. 定义工厂类,实现自抽象接口,并且实现抽象方法 BenzFactoryAudiFactoryBMWFactory
  5. new 工厂类,调用方法进行实例化

工厂方法模式增加了这么多的步骤,复杂度也高了不少,那么它解决了简单工厂的什么问题呢?

简单工厂在新增一款车的时候,需要修改简单工厂类 SimpleFactory,违背了设计模式中的“开闭原则”:对扩展开放,对修改封闭。

当工厂方法需要新增一款车的时候,比如 Ferrari,只需要定义自己的产品类 Ferrari 以及自己的工厂类 FerrariFactory

var Ferrari = function () {}
Ferrari.prototype = Object.create(Car.prototype)
Ferrari.prototype.constructor = Ferrari
Ferrari.prototype.drive = function () {
	console.log('Ferrari drive')
}

var FerrariFactory = function () {}
FerrariFactory.prototype = Object.create(IFactory.prototype)
FerrariFactory.prototype.constructor = FerrariFactory
FerrariFactory.prototype.getCar = function () {
	return new Ferrari()
}

var ferrariFactory = new FerrariFactory()
var ferrari = ferrariFactory.getCar()

ferrari.drive()				// Ferrari drive

不用再修改已有的抽象接口 IFactory,只需要扩展实现自己需要的就可以了,不会影响已有代码。这就是对扩展开放,对修改封闭

1.3 抽象工厂模式

抽象工厂模式:通过对类的工厂抽象,使其业务用于对产品类簇的创建,而不是负责创建某类产品的实例。

有了前一节工厂方法模式的基础,抽象工厂其实很类似,只不过工厂方法针对的是一个产品等级结构,而抽象工厂针对的是多个产品等级结构。

一起来解释 2 个概念:产品等级结构产品族

产品等级结构:

  • 产品等级结构就是产品的继承结构,比如一个抽象类是电视机,那么其子类会有海尔电视,TCL电视,小米电视等等,那么抽象电视机和具体品牌的电视机之间就构成了一个产品等级结构,抽象电视机是父类,具体品牌的电视机是子类。

产品族:

  • 在抽象工厂模式中,产品族是指由一个工厂生产的,位于不同产品等级结构中的一组产品,比如海尔电器工厂既生产海尔电视机,也生产海尔热水器,电视机和热水器位于不同的产品等级结构中,如果它们是由同一个工厂生产的,就称为产品族。

抽象工厂模式,提供一个创建一系列相关或相互依赖对象的接口,而无须指定它们具体的类。

抽象工厂模式包含如下 4 种角色:

  • 抽象工厂
  • 具体工厂
  • 抽象产品
  • 具体产品

文字上很难说清楚,下面一起来看看抽象工厂模式案例

上面的例子中我们的汽车厂商只能生产 Car,但是我们知道,很多汽车厂商也可以生产发动机 Engine,这就是两个产品等级结构,一个产品族。

我们先定义一个汽车厂商的抽象工厂 automakerFactory,提供 2 个抽象方法 createCarcreateEngine

var AutomakerFactory = function () {}

AutomakerFactory.prototype.createCar = function () {
	throw new Error('不能调用抽象方法,请自己实现')
}

AutomakerFactory.prototype.createEngine = function () {
	throw new Error('不能调用抽象方法,请自己实现')
}

然后定义具体工厂实现抽象工厂:


var BenzFactory = function () {}

BenzFactory.prototype = Object.create(AutomakerFactory.prototype)

BenzFactory.prototype.constructor = BenzFactory

BenzFactory.prototype.createCar = function () {
	return new BenzCar()
}

BenzFactory.prototype.createEngine = function () {
	return new BenzEngine()
}


var AudiFactory = function () {}

AudiFactory.prototype = Object.create(AutomakerFactory.prototype)

AudiFactory.prototype.constructor = AudiFactory

AudiFactory.prototype.createCar = function () {
	return new AudiCar()
}

AudiFactory.prototype.createEngine = function () {
	return new AudiEngine()
}

定义抽象产品类 Car 和 具体产品类 BenzCarAudiCar

var Car = function () {}
Car.prototype.drive = function () {
	throw new Error('不能调用抽象方法,请自己实现');
}

var BenzCar = function () {}
BenzCar.prototype = Object.create(Car.prototype)
BenzCar.prototype.constructor = BenzCar
BenzCar.prototype.drive = function () {
	console.log('Benz car drive')
}

var AudiCar = function () {}
AudiCar.prototype = Object.create(Car.prototype)
AudiCar.prototype.constructor = AudiCar
AudiCar.prototype.drive = function () {
	console.log('Audi car drive')
}

定义抽象产品类 Engine 和 具体产品类 BenzEngineAudiEngine

var Engine = function () {}
Engine.prototype.start = function () {
	throw new Error('不能调用抽象方法,请自己实现');
}

var BenzEngine = function () {}
BenzEngine.prototype = Object.create(Engine.prototype)
BenzEngine.prototype.constructor = BenzEngine
BenzEngine.prototype.start = function () {
	console.log('Benz engine start')
}

var AudiEngine = function () {}
AudiEngine.prototype = Object.create(Engine.prototype)
AudiEngine.prototype.constructor = AudiEngine
AudiEngine.prototype.start = function () {
	console.log('Audi engine start')
}

将抽象工厂的四种角色创建完成后,来看一下实例化过程:

var benz = new BenzFactory()
var benzCar = benz.createCar()
var benzEngine = benz.createEngine()

var audi = new AudiFactory()
var audiCar = audi.createCar()
var audiEngine = audi.createEngine()

benzCar.drive()						// Benz car drive
benzEngine.start()					// Benz engine start

audiCar.drive()						// Audi car drive
audiEngine.start()					// Audi engine start

通过一张图例来梳理下以上代码,加深理解:

JavaScript设计模式-创建型-工厂模式-黄继鹏博客

使用抽象工厂能为我们解决什么问题呢?

比如现在需要增加另一个厂商 BMW ,我们需要怎么做:

  1. 新增 BMW 具体工厂类
var BMWFactory = function () {}

BMWFactory.prototype = Object.create(AutomakerFactory.prototype)

BMWFactory.prototype.constructor = BMWFactory

BMWFactory.prototype.createCar = function () {
	return new BMWCar()
}

BMWFactory.prototype.createEngine = function () {
	return new BMWEngine()
}

  1. 新增 BMW 具体产品类
var BMWCar = function () {}
BMWCar.prototype = Object.create(Car.prototype)
BMWCar.prototype.constructor = BMWCar
BMWCar.prototype.drive = function () {
	console.log('BMW car drive')
}

var BMWEngine = function () {}
BMWEngine.prototype = Object.create(Engine.prototype)
BMWEngine.prototype.constructor = BMWEngine
BMWEngine.prototype.start = function () {
	console.log('BMW engine start')
}

  1. 实例化对象
var bmw = new BMWFactory()
var bmwCar = bmw.createCar()
var bmwEngine = bmw.createEngine()

bmwCar.drive()			// BMW car drive
bmwEngine.start()		// BMW engine start

增加完新的工厂后,会发现,抽象工厂和工厂方法一样,也是不需要修改已有工厂类,只需要新增自己的具体工厂类和具体产品类就可以了,也符合“开闭原则”,只不过和工厂方法不同的是,抽象工厂针对的是一类产品族中的不同产品等级结构这种场景,而工厂方法只是针对于一种产品等级结构的场景。

它的缺点也和工厂方法一样,如果不断的添加新产品会导致类越来越多,增加了系统复杂度。

2. 总结

  1. 简单工厂模式

    • 解决了用户多次自己实例化的问题,屏蔽细节,提供统一工厂,将实例化的过程封装到内部,提供给用户统一的方法,只需要传递不同的参数就可以完成实例化过程,有利于软件结构体系的优化;
    • 但不足之处是,增加新的子类时,需要修改工厂类,违背了“开闭原则”,并且工厂类会变得越来越臃肿;
    • 简单工厂模式适用于固定的,不会频繁新增子类的使用场景
  2. 工厂方法模式

    • 通过在上层再增加一层抽象,提供了接口,每个子类都有自己的工厂类,工厂类实现自接口,并且实现了统一的抽象方法,这样在新增子类的时候,完全不需要修改接口,只需要新增自己的产品类和工厂类就可以了,符合“开闭原则”;
    • 但不足之处也正是如此,持续的新增子类,导致系统类的个数将成对增加,在一定程度上增加了系统的复杂度,同时有更多的类需要编译和运行,会给系统代理一些额外的开销;
    • 工厂方法模式适用于会频繁新增子类的复杂场景;
  3. 抽象工厂模式

    • 抽象工厂模式提供一个创建一系列相关或相互依赖对象的接口,而无须指定它们具体的类。抽象工厂模式又称为Kit模式,属于对象创建型模式
    • 抽象工厂模式包含 4 种角色:
      • 抽象工厂、具体工厂、抽象产品、具体产品
      • 抽象工厂用于声明生成抽象产品的方法
      • 具体工厂实现了抽象工厂声明的生成抽象产品的方法,生成一组具体产品,这些产品构成了一个产品族,每一个产品都位于某个产品等级结构中
      • 抽象产品为每种产品声明接口,在抽象产品中定义了产品的抽象业务方法
      • 具体产品定义具体工厂生产的具体产品对象,实现抽象产品接口中定义的业务方法
    • 抽象工厂模式与工厂方法模式最大的区别在于,工厂方法模式针对的是一个产品等级结构,而抽象工厂模式则需要面对多个产品等级结构
    • 抽象工厂模式的主要优点是隔离了具体类的生成,使得客户并不需要知道什么被创建,而且每次可以通过具体工厂类创建一个产品族中的多个对象,增加或者替换产品族比较方便,增加新的具体工厂和产品族很方便;主要缺点在于增加新的产品等级结构很复杂,需要修改抽象工厂和所有的具体工厂类,导致系统复杂度增加
    • 抽象工厂模式适用情况包括:一个系统不应当依赖于产品类实例如何被创建、组合和表达的细节;系统中有多于一个的产品族,而每次只使用其中某一产品族;属于同一个产品族的产品将在一起使用;系统提供一个产品类的库,所有的产品以同样的接口出现,从而使客户端不依赖于具体实现
0条评论
...