工厂模式
工厂模式是用来创建对象的一种常用的设计模式。在使用该模式时,我们不去暴露创建对象的具体逻辑,而是将逻辑封装到一个函数中,那么该函数就会被视为一个工厂,从而能够解决创建相似对象的问题。工厂模式可以分为:简单工厂、工厂方法、抽象工厂。
1.简单工厂模式
简单工厂模式又叫静态工厂模式。它是由一个工厂对象决定某一种产品对象类的实例。主要是用来创建同一类对象。
在实际中的例子中,我们常常需要根据用户的权限来渲染不同的页面。譬如低权限的用户无法看到高权限用户能看到内容。我们可以用一个User类,通过传入的参数对不同用户身份进行判断,从而得到拥有不同权限的User实例。
class User {
constructor(opt) {
this.identity = opt.identity
this.viewPage = opt.viewPage
}
static getInstance(identity) {
switch (identity) {
case 'simple':
return new User({ identity: 'simple', viewPage: ['首页', '通讯录', '发现页'] });
break;
case 'admin':
return new User({ identity: 'admin', viewPage: ['首页', '通讯录', '发现页', '应用数据'] });
break;
default:
break;
}
}
}
let simpleUser = User.getInstance('simple')
let adminUser = User.getInstance('admin')
console.log(simpleUser, adminUser)
在上述实现中,User类实际上就是一个简单工厂,我们只需要通过给类的静态方法getInstance传入规定的参数(simple/admin)便可以控制不同用户看到的页面内容。解决了生成相似实例的问题(都是同属于User类,但拥有不同的权限)
简单工厂的优点在于,只需要一个正确的参数,便可以获取到我们所需要的对象,而无需知道创建该对象的具体细节。将类的实例化交给工厂函数去做,对外提供统一的方法。在代码中new是一个需要慎重考虑的操作,new出现的次数越多,代码的耦合性就越强,可维护性就越差,简单工厂便是在上面做了一层抽象,将new的操作封装了起来,向外提供静态方法供用户调用,这样就将耦合集中到了工厂函数中,而不是暴露在代码的各个位置。
但当我们需要新增一个类型的实例时(以上述例子说明),必须要去修改工厂的静态方法,且当用户身份很多时,getInstance方法便会变得非常庞大,会因此变得难以维护。故简单工厂只能作用于创建的对象身份少且对象创建逻辑不复杂时。
2.工厂方法模式
在工厂方法模式中,我们不再提供一个统一的工厂类来创建所有对象,而是针对不同的对象提供不同的工厂,也就是说每个对象都有一个与之对应的工厂。
工厂方法模式实际上是将实际创建对象的工作推迟到子类中。这样工厂核心类便变成了抽象类。由于js中没有实现abstract,故我们需要使用new.target来模拟抽象类(new.target直接指向new执行的构造函数),我们可以对new.target进行判断,如果它指向工厂核心类,则抛出错误。
class Car {
drive() {
console.log('Car drive')
}
}
class Benz extends Car {
drive() {
console.log('Benz drive')
}
}
class BYD extends Car {
drive() {
console.log('BYD drive')
}
}
class BMW extends Car {
drive() {
console.log('BMW drive')
}
}
class IFactory {
getCar() {
throw new Error('不允许直接调用抽象方法,请自己实现')
}
}
class BenzFactory extends IFactory {
getCar() {
return new Benz();
}
}
class BYDFactory extends IFactory {
getCar() {
return new BYD();
}
}
class BMWFactory extends IFactory {
getCar() {
return new BMW();
}
}
let benzFactory = new BenzFactory();
let benz = benzFactory.getCar();
let bydFactory = new BYDFactory();
let byd = bydFactory.getCar();
let bmwFactory = new BMWFactory();
let bmw = bmwFactory.getCar();
benz.drive(); // Benz drive
byd.drive(); //BYD drive
bmw.drive(); // BMW drive
上面例子中,我们通过不同工厂来创建不同类型的汽车,当有新的类型的汽车需要被IFactory工厂接纳时,只需要为其创建属于自己的工厂类,即创建一个新的类去继承IFactory并创建实现Car类的汽车类。这样便不会因为新的类型去修改IFactory工厂类的内容,也更加符合开放封闭原则.
3.抽象工厂模式
抽象工厂模式实际上和工厂方法模式很类似,不过抽象工厂模式是针对某一个产品簇,而工厂方法模式是针对某一个产品。举例而言:对于一个工厂,不止可以生产汽车,也可以生产电器,这样的话便需要创建一个针对产品簇的抽象工厂,而当有新的品牌加入时,直接实现该抽象工厂即可。但我们也需要注意的是,为了符合开放封闭原则,我们需要创建抽象产品类, 抽象工厂模式的官方定义:提供一个创建一系列相关或相互依赖对象的接口,而无须指定它们具体的类
抽象工厂模式包含以下4种角色:
- 抽象工厂
- 具体工厂
- 抽象产品
- 具体产品
//抽象工厂模式
在上述例子中,如果我们需要新增一个工厂AUDI,我们需要做什么呢://抽象引擎产品类 class Engine { start () { throw new Error('不能调用抽象方法,请自己实现'); } } //具体引擎产品类 class BenzEngine extends Engine { start() { console.log('Benz engine') } } class BYDEngine extends Engine { start() { console.log('BYD engine') } } //抽象汽车产品类 class Car { drive() { throw new Error('不能调用抽象方法,请自己实现'); } } //具体汽车产品类 class BenzCar extends Car { drive() { console.log('Benz drive') } } class BYDCar extends Car { drive() { console.log('BYD drive') } } //抽象工厂类 class AutoMakerFactory { createCar() { throw new Error('不能调用抽象方法,请自己实现') } createEngine() { throw new Error('不能调用抽象方法,请自己实现') } } //具体工厂类 class BenzFactory extends AutoMakerFactory { createCar() { return new BenzCar(); } createEngine() { return new BenzEngine(); } } class BYDFactory extends AutoMakerFactory { createCar() { return new BYDCar(); } createEngine() { return new BYDEngine(); } } let benzFactory = new BenzFactory(); let benzCar = benzFactory.createCar(); let benzEngine = benzFactory.createEngine(); let bydFactory = new BYDFactory(); let bydCar = bydFactory.createCar(); let bydEngine = bydFactory.createEngine(); benzCar.drive(); benzEngine.start(); bydCar.drive(); bydEngine.start(); console.log(benzCar,benzEngine,bydCar,bydEngine);
① 创建AUDI具体工厂类(实现AutoMakerFactory抽象工厂类)
class AUDIFactory extends AutoMakerFactory {
createCar() {
return new AUDICar();
}
createEngine() {
return new AUDIEngine();
}
}
② 创建AUDI具体产品类(AUDICar、AUDIEngine)
class AUDICar extends Car {
drive() {
console.log('AUDICar drive')
}
}
class AUDIEngine extends Engine {
start() {
console.log('AUDIEngine start')
}
}
③ 实例化
let audiFactory = new AUDIFactory();
let audiCar = audiFactory.createCar();
let audiEngine = audiFactory.createEngine();
audiCar.drive();
audiEngine.start();
从上除例子可以看出,当新增一个工厂类时,无需修改抽象工厂类的代码,只需要添加新的工厂类,让其实现抽象工厂类即可,符合"开闭原则"。我们可以在具体工厂类中去实例化不同产品类对象,这样便可实现一个工厂类对应一个产品簇。当然它的缺点也和工厂方法一样,不断地添加新产品会导致类越来越多,增加系统复杂度。
4. 总结
简单工厂模式
解决了用户多次自己实例化的问题,屏蔽细节,提供统一工厂,将实例化的过程封装到内部,提供给用户统一的方法,只需要传递不同的参数就可以完成实例化过程,有利于软件结构体系的优化; 不足之处是,增加新的子类时,需要修改工厂类,违背了“开闭原则”,并且工厂类会变得越来越臃肿; 简单工厂模式适用于固定的,不会频繁新增子类的使用场景
工厂方法模式
通过在上层再增加一层抽象,提供了接口,每个子类都有自己的工厂类,工厂类实现自接口,并且实现了统一的抽象方法,这样在新增子类的时候,完全不需要修改接口,只需要新增自己的产品类和工厂类就可以了,符合“开闭原则”; 但不足之处也正是如此,持续的新增子类,导致系统类的个数将成对增加,在一定程度上增加了系统的复杂度,同时有更多的类需要编译和运行,会给系统代理一些额外的开销; 工厂方法模式适用于会频繁新增子类的复杂场景;
抽象工厂模式
抽象工厂隔离了具体类地生成,使得客户并不需要知道什么被创建,而且每次可以通过具体工厂类创建一个产品簇中的多个对象,增加或替换产品簇比较方便,增加新的具体工厂和产品簇也很方便; 但不足之处和工厂方法一致,持续的新增子类,导致系统类的个数将成对增加,在一定程度上增加了系统的复杂度,同时有更多的类需要编译和运行,会给系统代理一些额外的开销; 抽象工厂适用情况:① 一个系统不应当依赖于产品类实例如何被创建、组合和表达的细节;② 属于同一个产品簇的产品将在一起使用;③ 系统提供一个产品类的库,所有的产品以同样的接口出现,从而使得客户端不依赖于具体实现。