本文采用 Typescript 进行 demo 演示
1. 简单工厂
简单工厂又名静态工厂,以创建实例的方法为静态方法而得名。 简单工厂聚合了不同类的实例化工作,将实例化的工作从实际的业务代码中剥离,只需要调用该类,传入对应的参数得到对应的实例化对象。
故事背景: 已知我们有一个 Coffee 类,这个 Coffee 的抽象咖啡概念里有 Latte, Moca, Cappuccino 具体的几类咖啡,我们来实现一下代码。
我们首先定义一下故事中的各个角色:
Coffee —— 抽象产品类
Latte, Moca, Cappuccino ——— 具体实现抽象产品类 Coffee 的产品子类
CoffeeFactory —— 生产具体产品实例的工厂类
对应关系的 UML 图:
1.1 Code it
// 定义一个抽象的 Coffee 类。
abstract class Coffee {
name:string
show() {
console.log(`this is ${this.name}`);
}
}
// Coffee 具体实现类 Latte
class Latte extends Coffee {
constructor(){
// 由于继承了抽象类,需要调用 super 来获取 this
super()
this.name = 'Latte'
}
}
// Coffee 具体实现类 Moca
class Moca extends Coffee{
constructor(){
super()
this.name = 'Moca'
}
}
// Coffee 具体实现类 Cappuccino
class Cappuccino extends Coffee{
constructor(){
super()
this.name = 'Cappuccino'
}
}
当我们业务里面需要 Latte 的时候,我们需要自己去实例化 Latte,如果实例化 Latte 需要一大堆前置动作,那对于业务来说是一个沉重的负担。所以我们需要一个 Coffee Factory 来帮我们生成我们想要的 Latte 实例,我们只需要告知工厂我们需要一个 Latte,它就可以帮我们生成一个 Latte 实例。
// 工厂类
class CoffeeFactory {
// 静态方法获取相应的对象实例
static getCoffee(type?: string): Coffee {
// 根据传入参数,判断应该返回什么样的实例给调用方
switch (type) {
case "latte":
return new Latte()
case "moca":
return new Moca();
case "cappuccino":
return new Cappuccino()
default:
console.log('no coffee provided')
return;
}
}
}
// 业务方
CoffeeFactory.getCoffee('latte'); // 业务方调用工厂,拿到相应的实例
1.2 Good & Bad
Good
- 工厂类里面包含着什么时机创建什么实例对象的任务,调用方无需关心实例化的细节和创建任务判断,实现了和业务方的解耦。
- 调用方只需要知道参数即可,业务方无需关心具体实例化的细节。 Bad
- 工厂类里面包含了所有类的实例化逻辑,一旦需要新增一个类,e.g. 当需要新增一个
Coffee类目——云顶咖啡,那么就需要修改工厂类,不符合设计原则中的开闭原则。 - 由于工厂类使用了静态方法(static)来创建实例,导致工厂类无法形成基于继承的等级结构。
1.3 适用场景
类目较少的情况下,可以考虑采用简单工厂来简化调用方实例化对象的过程。
2. 工厂模式
工厂模式是简单工厂的进一步抽象,工厂父类不再承担创建产品的任务,而是由工厂子类来承担。
故事背景: 当我们工厂类增加时,例如我们有好几个咖啡工厂分布在不同地域,不同咖啡工厂可以生产的咖啡种类不同,这时候,我们就需要拓展我们的工厂类,来满足我们不同地域咖啡的组合需求了。
Coffee —— 抽象产品类
Latte, Moca, Cappuccino ——— 具体的 Coffee 子类
CoffeeFactory —— 核心工厂
ChinaFactory,JanpanFactory —— 具体实现抽象工厂 CoffeeFactory 的工厂子类
对应的关系 UML 图:
2.1 Code it
// 改造上一小节的 CoffeeFactory 改造为一个抽象工厂,核心工厂类
abstract class CoffeeFactory {
name: string;
coffee:Coffee;
abstract produceCoffee(): void;
getCoffeeList() {
console.log(`${this.name} can produce: `);
this.coffee.getName()
}
}
// 具体实现核心工厂类的 日本工厂子类
class JapanCoffeeFactory extends CoffeeFactory {
constructor(name: string) {
super();
this.name = name;
}
produceCoffee() {
// 创建咖啡子类 Latte 实例
this.coffee = new Latte()
}
}
// 具体实现核心工厂类的 中国工厂子类
class ChinaCoffeeFactory extends CoffeeFactory {
constructor(name: string) {
super();
this.name = name;
}
produceCoffee() {
// 创建咖啡子类 Moca 实例
this.coffee = new Moca()
}
}
const China = new ChinaCoffeeFactory("shenzhen");
const Japan = new JapanCoffeeFactory("toshita");
China.produceCoffee();
China.getCoffeeList(); // 业务方获取中国工厂生产的 Moca 咖啡实例
Japan.produceCoffee();
Japan.getCoffeeList(); // 业务方获取日本工厂生产的 Latte 咖啡实例
最后运行结果:
2.2 Good & Bad
Good
- 相比起简单工厂,在加入新产品时,都需要修改工厂类;采用工厂模式则可以无需修改抽象工厂和抽象产品的接口,只需要提供一个具体的工厂和具体产品即可,提高了系统的可扩展性。
- 满足单一职责原则,工厂父类不再负责判断、创建的一系列工作,只定义好了创建对象的接口。由工厂子类则负责生成具体的产品对象,将实例化的操作延迟至不同的工厂子类中完成。e.g. 中国咖啡厂和日本咖啡厂都是咖啡厂,但是中国咖啡厂可以生产三种咖啡,而日本只能生产两种,这完全取决于各个具体工厂。 Bad
- 每个具体工厂只能生产具体的一种产品
- 每次新增产品时,都需要编写具体的产品类以及其对应的具体工厂类,系统内的类数会成对增加。
2.3 适用场景
- 一个类无需知道其依赖的具体对象类,只需要知道其工厂即可,具体产品对象由具体工厂进行创建。e.g. 业务方需要咖啡这个模糊概念,但是它可以无需关心是具体什么咖啡(Latte or Moca...),只要在需要时,调用某个工厂生成所需的咖啡即可,无须关心是哪一个工厂子类创建产品子类,需要时再动态指定。
- 一个工厂父类只需要提供一个创建产品的接口,而由他的具体工厂子类来是确定、实现具体要创建的对象,将创建子类的任务委托给子类工厂。
3. 抽象工厂模式
抽象工厂是工厂模式的进一步推广,抽象。前面提到的工厂模式中,更多考虑的是一类产品的生产,但是显示生活中,一个工厂往往承担不止一个产品的生产。抽象工厂模式中,提供一个创建一系列相关或相互依赖对象的接口,而无须指定它们具体的类,属于对象创建型模式。
故事背景: 当业务扩张之后,一个工厂不局限于生成咖啡了,可能会开始涉猎其他的饮料品类生产,例如茶、苏打水等等,不同工厂主打的方向也不一样,这时候一个咖啡工厂已经不满足实际业务需要了。
Coffee, Tea, Sodas —— 抽象饮料类,咖啡、茶、苏打水
Latte, Moca, Cappuccino ——— 具体咖啡的某一类
PuEr ——— 具体茶的某一类
Cola ——— 具体苏打水的某一类
DrinkingFactory —— 一个抽象的饮料工厂,只定义生产抽象饮料类的接口
ChinaFactory,JanpanFactory —— 具体某个饮料工厂,生产具体的饮料实例
对应的关系 UML 图 (画图偷懒,未一一对应):
3.1 Code it
import { Latte, Moca, Coffee } from "./coffee-factory";
// 不同品类: Sodas 抽象类
abstract class Sodas {
name: string;
show() {
console.log(`this is ${this.name}`);
}
}
// Sodas 品类下具体的 Cola 子类
class Cola extends Sodas {
constructor() {
super();
this.name = "Cola";
}
}
// 不同品类: Tea 抽象类
abstract class Tea {
name: string;
show() {
console.log(`this is ${this.name}`);
}
}
// Tea 品类下具体的 普洱 子类
class PuEr extends Tea {
constructor() {
super();
this.name = "普洱";
}
}
// 改造工厂模式中的核心工厂类,改为抽象工厂
// 提供抽象接口方法,创建抽象产品品类,供工厂子类具体实现内部细节
abstract class DrinkingFactory {
name: string;
// 生产 Coffee 抽象品类的抽象方法
abstract produceCoffee(): Coffee;
// 生产 Sodas 抽象品类的抽象方法
abstract produceSodas(): Sodas;
// 生产 Tea 抽象品类的抽象方法
abstract produceTea(): Tea;
getList(): void {
console.log(`${this.name} produce:`);
console.log(this.produceCoffee());
console.log(this.produceSodas());
console.log(this.produceTea());
}
}
// 具体工厂子类实现抽象工厂生成抽象品类的细节
class JapanFactory extends DrinkingFactory {
name: string;
constructor(name: string) {
super();
this.name = name;
}
// 具体工厂子类实现生产 Coffee 品类的抽象方法
produceCoffee(): Coffee {
return new Moca();
}
// 具体工厂子类实现生产 Sodas 品类的抽象方法
produceSodas(): Sodas {
return new Cola();
}
// 具体工厂子类实现生产 Tea 品类的抽象方法
produceTea(): Tea {
return null;
}
}
class ChinaFactory extends DrinkingFactory {
name: string;
constructor(name: string) {
super();
this.name = name;
}
produceCoffee(): Coffee {
return new Latte();
}
produceSodas(): Sodas {
return new Cola();
}
produceTea(): Tea {
return new PuEr();
}
}
const Japan = new JapanFactory("toshida");
const China = new ChinaFactory("Shanghai");
Japan.getList(); // 调用方不关心内部实现细节,通过核心工厂接口获取所需内容
China.getList();
最后运行结果:
3.2 Good & Bad
Good
- 抽象工厂模式隔离了具体类的生产,调用方不知道什么需要被创建,具体工厂都继承于公共的抽象工厂,实现了抽象工厂定义的公共接口。
- 新增具体工厂或者某一族类的产品变得简单,无需修改已有的系统。 Bad
- 新增新的族类产品时,难以拓展抽象工厂来生产新的族类,例如再新增了一个八宝粥系列,那就需要修改抽象工厂的内容。支持新的品类则需要对抽象工厂接口作出修改,同时又会涉及其他工厂子类的修改,带来更大的修改风险。
- 开闭原则的倾斜性,对于新增工厂、新增产品类目相对简单,但是对于新增新的产品品类则比较麻烦。例如,在茶品类下实现奶茶,相对简单,但是要新增和茶、咖啡、苏打水同一等级的品类时,则相对复杂。
- 当只有一个品类时,抽象工厂将会退化为普通的工厂模式,如第二小节所示。
3.3 适用场景
- 系统提供一个品类的库,所有的产品以同样的接口出现,从而使调用方无需依赖于具体实现。
- 隔离了具体产品类的生成,每次通过具体工厂创建一个品类中的对个类型时比较简单,新增新的工厂或者类目时比较简单。
4. 小结
4.1 简单工厂 & 工厂模式
工厂模式是对简单工厂的进一步推广和抽象,使用了面向对象的多态性,同时保留了简单工厂的优点。在工厂模式中,核心工厂不再负责产品的创建,转而提供创建产品的接口;由工厂子类实现创建细节,将创建产品的时机延迟至子类中实现,将创建过程封装于该类工厂中。这使得工厂模式允许在不修改核心工厂的情况下引入新的产品。
而工厂模式的缺点,通常情况下,产品和工厂是一一对应的关系,于是当引入新的产品时,还要再引入新的生产该产品的工厂类。换言之,在新增产品时,引入类的数量是成对增加的,会提高系统的复杂度。
4.2 工厂模式 & 抽象工厂
抽象工厂是在工厂模式的基础上进一步的拓展。前面提及的工厂模式仅限于同一类产品的生产创建,而抽象工厂面向了不同类产品的生产创建,当系统中只有一个品类时,抽象工厂将退化为工厂模式。
抽象工厂的主要优点是隔离了具体类的生成,使得客户端不需要是什么要被创建,每次只需要通过具体的工厂拿到创建该品类下的产品即可,新增工厂或者新增某个品类下产品变得简单。但是抽象工厂具有带倾斜性的开闭原则支持。当需要新增不同品类时,需要修改的范围包括核心工厂和工厂子类。增加不同品类的会变得复杂。
For more information: