分类
这里工厂模式分为2类:简单工厂 和 工厂方法,下一节会介绍第3类工厂模式:抽象工厂。
简单工厂
定义
简单工厂:定义一个类来创建其他类的实例,根据参数的不同返回不同类的实例,通常这些类拥有相同的父类。
例子
假设现在有 3 款车,Benz、Audi 和 BMW,他们都继承自父类 Car,并且重写了父类方法 drive:
class Car {
drive () {
console.log('Car drive');
}
}
class Benz extends Car {
drive () {
console.log(`Benz drive`)
}
}
class Audi extends Car {
drive () {
console.log(`Audi drive`)
}
}
class BMW extends Car {
drive () {
console.log(`BMW drive`)
}
}
我们定义了一个父类 Car,包含一个方法 drive,Benz、Audi 和 BMW 继承自共同的父类 Car。
那么我们在实例化这 3 款车的时候,就需要如下调用:
let benz = new Benz();
let audi = new Audi();
let bmw = new BMW();
benz.drive(); // Benz drive
audi.drive(); // Audi drive
bmw.drive(); // BMW drive
这种写法就很繁琐,这时候就用到我们的简单工厂了,提供一个工厂类:
class SimpleFactory {
static getCar (type) {
switch (type) {
case 'benz':
return new Benz();
case 'audi':
return new Audi();
case 'bmw':
return new BMW();
}
}
}
简单工厂类 SimpleFactory 提供一个静态方法 getCar,我们再实例化 3 款车的时候,就变成下面这样了:
let benz = SimpleFactory.getCar('benz');
let audi = SimpleFactory.getCar('audi');
let bmw = SimpleFactory.getCar('bmw');
benz.drive(); // Benz drive
audi.drive(); // Audi drive
bmw.drive(); // BMW drive
这么一看,使用简单工厂后代码行数反而变多了,这种写法真的有优势吗?
我们要知道,设计模式并不是为了减少代码行数而出现的,它是为了使我们的代码更好扩展,更好维护,更方便使用而出现的。那么使用简单工厂后,有什么好处呢?
简单工厂,用户不需要知道具体产品的类名,只需要知道对应的参数即可,对于一些复杂的类名,可以减少用户的记忆量,同时用户无需了解这些对象是如何创建及组织的,有利于整个软件体系结构的优化。
所以,使用简单工厂,是将类的实例化交给工厂函数去做,对外提供统一的方法。我们要养成一个习惯,在代码中 new 是一个需要慎重考虑的操作,new 出现的次数越多,代码的耦合性就越强,可维护性就越差,简单工厂,就是在上面做了一层抽象,将 new 的操作封装了起来,向外提供静态方法供用户调用,这样就将耦合集中到了工厂函数中,而不是暴露在代码的各个位置。
缺陷
简单工厂有它的好处,必然也有它的缺点,比如下面这种情况:
我们又新增了一类车 Ferrai,就需要在简单工厂类 SimpleFactory 中再新增一个 case,添加 Ferrari 的实例化过程。每新增一类车,就要修改简单工厂类,这样做其实违背了设计原则中的开闭原则:对扩展开放,对修改封闭,同时如果每类车在实例化之前需要做一些处理逻辑的话,SimpleFactory 会变的越来越复杂。
所以简单工厂适用于产品类比较少并且不会频繁增加的情况,那么有什么方法能解决简单工厂存在的问题呢?
工厂方法
工厂方法模式是简单工厂的进一步优化,我们不再提供一个统一的工厂类来创建所有的对象,而是针对不同的对象提供不同的工厂,也就是说每个对象都有一个与之对应的工厂。
定义
工厂方法模式又称为工厂模式,它的实质是“定义一个创建对象的接口,但让实现这个接口的类来决定实例化哪个类,工厂方法让类的实例化推迟到子类中执行”。
例子
光看概念还是很难理解,我们沿着上面的简单工厂来做修改,举例说明。
还是先定义一个父类 Car,提供一个方法 drive:
class Car {
drive () {
console.log('Car drive');
}
}
每一款车都继承自父类 Car,并重写方法 drive:
class Benz extends Car {
drive () {
console.log(`Benz drive`)
}
}
class Audi extends Car {
drive () {
console.log(`Audi drive`)
}
}
class BMW extends Car {
drive () {
console.log(`BMW drive`)
}
}
然后按照定义,我们提供一个创建对象的接口:
class IFactory {
getCar () {
throw new Error('不允许直接调用抽象方法,请自己实现');
}
}
每款车都提供一个工厂类,实现自上述接口,因为 JavaScript 中没有 implements,所以用 extends 代替:
class BenzFactory extends IFactory {
getCar () {
return new Benz();
}
}
class AudiFactory extends IFactory {
getCar () {
return new Audi();
}
}
class BMWFactory extends IFactory {
getCar () {
return new BMW();
}
}
这样当我们需要实例化每款车的时候,就按如下操作:
let benzFactory = new BenzFactory();
let benz = benzFactory.getCar();
let audiFactory = new AudiFactory();
let audi = audiFactory.getCar();
let bmwFactory = new BMWFactory();
let bmw = bmwFactory.getCar();
benz.drive(); // Benz drive
audi.drive(); // Audi drive
bmw.drive(); // BMW drive
我们再来对比一下简单工厂的实例化过程:
let benz = SimpleFactory.getCar('benz');
let audi = SimpleFactory.getCar('audi');
let bmw = SimpleFactory.getCar('bmw');
benz.drive(); // Benz drive
audi.drive(); // Audi drive
bmw.drive(); // BMW drive
我们用 UML 类图来描述一下工厂方法模式:
可以看到,同样是为了实例化一个对象,怎么就变得这么复杂了呢····我们来总结一下工厂方法模式的步骤:
- 定义产品父类 -- Car
- 定义子类实现父类,并重写父类方法 -- BenzCar、AudiCar、BMWCar
- 定义抽象接口,以及抽象方法 -- IFactory
- 定义工厂类,实现自抽象接口,并且实现抽象方法 -- BenzFactory、AudiFactory、BMWFactory
- new 工厂类,调用方法进行实例化
那么工厂方法增加了如此多的流程,提高了复杂度,究竟解决了简单工厂的什么问题呢?
通过上文可以知道,简单工厂在新增一款车的时候,需要修改简单工厂类 SimpleFactory,违背了设计模式中的“开闭原则”:对扩展开放,对修改封闭。
当工厂方法需要新增一款车的时候,比如 Ferrari,只需要定义自己的产品类 Ferrari 以及自己的工厂类 FerrariFactory:
class Ferrari extends Car {
drive () {
console.log(`Ferrari drive`)
}
}
class FerrariFactory extends IFactory {
getCar () {
return new Ferrari();
}
}
let ferrariFactory = new FerrariFactory();
let ferrari = ferrariFactory.getCar();
ferrari.drive(); // Ferrari drive
完全不用修改已有的抽象接口 IFactory,只需要扩展实现自己需要的就可以了,不会影响已有代码。这就是对扩展开放,对修改封闭。
总结
-
简单工厂模式
- 解决了用户多次自己实例化的问题,屏蔽细节,提供统一工厂,将实例化的过程封装到内部,提供给用户统一的方法,只需要传递不同的参数就可以完成实例化过程,有利于软件结构体系的优化;
- 但不足之处是,增加新的子类时,需要修改工厂类,违背了“开闭原则”,并且工厂类会变得越来越臃肿;
- 简单工厂模式适用于固定的,不会频繁新增子类的使用场景
-
工厂方法模式
- 通过在上层再增加一层抽象,提供了接口,每个子类都有自己的工厂类,工厂类实现自接口,并且实现了统一的抽象方法,这样在新增子类的时候,完全不需要修改接口,只需要新增自己的产品类和工厂类就可以了,符合“开闭原则”;
- 但不足之处也正是如此,持续的新增子类,导致系统类的个数将成对增加,在一定程度上增加了系统的复杂度,同时有更多的类需要编译和运行,会给系统代理一些额外的开销;
- 工厂方法模式适用于会频繁新增子类的复杂场景;