阅读 235

JavaScript设计模式第3篇:抽象工厂模式

作者:leocoder
仓库:Github
博客:掘金思否
公众号:学如登山

接着上一篇《JavaScript设计模式第2篇:工厂模式》,今天我们来看工厂模式的最后一种:抽象工厂

定义

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

我们先来解释 2 个概念:产品等级结构产品族

产品等级结构:

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

产品族:

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

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

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

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

概念还是太生涩了,下面看例子。

例子

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

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

class AutomakerFactory {
    createCar () {
        throw new Error('不能调用抽象方法,请自己实现');
    }
    
    createEngine () {
        throw new Error('不能调用抽象方法,请自己实现');
    }
}
复制代码

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

class BenzFactory extends AutomakerFactory {
    createCar () {
        return new BenzCar();
    }
    
    createEngine () {
        return new BenzEngine();
    }
}

class AudiFactory extends AutomakerFactory {
    createCar () {
        return new AudiCar();
    }
    
    createEngine () {
        return new AudiEngine();
    }
}
复制代码

定义抽象产品类 Car 和 具体产品类 BenzCar、AudiCar:

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

class BenzCar extends Car {
    drive () {
        console.log('Benz drive');
    }
}

class AudiCar extends Car {
    drive () {
        console.log('Audi drive');
    }
}
复制代码

定义抽象产品类 Engine 和 具体产品类 BenzEngine、AudiEngine:

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

class BenzEngine extends Engine {
    start () {
        console.log('Benz engine start');
    }
}

class AudiEngine extends Engine {
    start () {
        console.log('Audi engine start');
    }
}
复制代码

如上,抽象工厂需要的 4 种角色就创建完成了,我们来看一下实例化过程:

let benz = new BenzFactory();
let benzCar = benz.createCar();
let benzEngine = benz.createEngine();

let audi = new AudiFactory();
let audiCar = audi.createCar();
let audiEngine = audi.createEngine();

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

audiCar.drive();            // Audi drive
audiEngine.start();         // Audi engine start
复制代码

还是通过 UML 类图来加深一下理解:

通过抽象工厂我们解决了什么问题呢?

假设现在需要增加另一个厂商 BMW,我们需要做些什么:

  1. 新增 BMW 具体工厂类
class BMWFactory extends AutomakerFactory {
    createCar () {
        return new BMWCar();
    }
    
    createEngine () {
        return new BMWEngine();
    }
}
复制代码
  1. 新增 BMW 具体产品类
class BMWCar extends Car {
    drive () {
        console.log('BMW drive');
    }
}

class BMWEngine extends Engine {
    start () {
        console.log('BMW engine start');
    }
}
复制代码
  1. 实例化
let bmw = new BMWFactory();
let bmwCar = bmw.createCar();
let bmwEngine = bmw.createEngine();

bmwCar.drive();            // BMW drive
bmwEngine.start();         // BMW engine start
复制代码

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

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

总结

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