工厂模式是用来创建对象的一种常用的设计模式。在使用该模式时,我们不去暴露创建对象的具体逻辑,而是将逻辑封装到一个函数中,那么该函数就会被视为一个工厂,从而能够解决创建相似对象的问题。工厂模式可以分为:简单工厂
、工厂方法
、抽象工厂
。
简单工厂模式
又叫静态工厂模式
。它是由一个工厂对象决定某一种产品对象类的实例。主要是用来创建同一类对象。
在实际中的例子中,我们常常需要根据用户的权限来渲染不同的页面。譬如低权限的用户无法看到高权限用户能看到内容。我们可以用一个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
方法便会变得非常庞大,会因此变得难以维护。故简单工厂只能作用于创建的对象身份少且对象创建逻辑不复杂时。
在工厂方法模式中,我们不再提供一个统一的工厂类来创建所有对象,而是针对不同的对象提供不同的工厂,也就是说每个对象都有一个与之对应的工厂。
工厂方法模式实际上是将实际创建对象的工作推迟到子类中。这样工厂核心类便变成了抽象类。由于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
工厂类的内容,也更加符合开放封闭原则
抽象工厂模式实际上和工厂方法模式很类似,不过抽象工厂模式是针对某一个产品簇,而工厂方法模式是针对某一个产品。举例而言:对于一个工厂,不止可以生产汽车,也可以生产电器,这样的话便需要创建一个针对产品簇的抽象工厂,而当有新的品牌加入时,直接实现该抽象工厂即可。但我们也需要注意的是,为了符合开放封闭原则,我们需要创建抽象产品类,
抽象工厂模式的官方定义:提供一个创建一系列相关或相互依赖对象的接口,而无须指定它们具体的类
抽象工厂模式包含以下4种角色:
来看一个例子
//抽象工厂模式
//抽象引擎产品类
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,我们需要做什么呢:
① 创建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()
从上除例子可以看出,当新增一个工厂类时,无需修改抽象工厂类的代码,只需要添加新的工厂类,让其实现抽象工厂类即可,符合"开闭原则"。我们可以在具体工厂类中去实例化不同产品类对象,这样便可实现一个工厂类对应一个产品簇。当然它的缺点也和工厂方法一样,不断地添加新产品会导致类越来越多,增加系统复杂度。
简单工厂模式
工厂方法模式
抽象工厂模式
参考文章:
JavaScript设计模式与实践–工厂模式
JavaScript设计模式第2篇:工厂模式
JavaScript设计模式第3篇:抽象工厂模式