设计模式 | 工厂模式

249 阅读8分钟

本文采用 Typescript 进行 demo 演示

1. 简单工厂

简单工厂又名静态工厂,以创建实例的方法为静态方法而得名。 简单工厂聚合了不同类的实例化工作,将实例化的工作从实际的业务代码中剥离,只需要调用该类,传入对应的参数得到对应的实例化对象。

故事背景: 已知我们有一个 Coffee 类,这个 Coffee 的抽象咖啡概念里有 Latte, Moca, Cappuccino 具体的几类咖啡,我们来实现一下代码。

我们首先定义一下故事中的各个角色:

Coffee —— 抽象产品类

Latte, Moca, Cappuccino ——— 具体实现抽象产品类 Coffee 的产品子类

CoffeeFactory —— 生产具体产品实例的工厂类

对应关系的 UML 图:

image.png

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

  1. 工厂类里面包含着什么时机创建什么实例对象的任务,调用方无需关心实例化的细节和创建任务判断,实现了和业务方的解耦。
  2. 调用方只需要知道参数即可,业务方无需关心具体实例化的细节。 Bad
  3. 工厂类里面包含了所有类的实例化逻辑,一旦需要新增一个类,e.g. 当需要新增一个 Coffee 类目——云顶咖啡,那么就需要修改工厂类,不符合设计原则中的开闭原则。
  4. 由于工厂类使用了静态方法(static)来创建实例,导致工厂类无法形成基于继承的等级结构。

1.3 适用场景

类目较少的情况下,可以考虑采用简单工厂来简化调用方实例化对象的过程。

2. 工厂模式

工厂模式是简单工厂的进一步抽象,工厂父类不再承担创建产品的任务,而是由工厂子类来承担。

故事背景: 当我们工厂类增加时,例如我们有好几个咖啡工厂分布在不同地域,不同咖啡工厂可以生产的咖啡种类不同,这时候,我们就需要拓展我们的工厂类,来满足我们不同地域咖啡的组合需求了。

Coffee —— 抽象产品类

Latte, Moca, Cappuccino ——— 具体的 Coffee 子类

CoffeeFactory —— 核心工厂

ChinaFactory,JanpanFactory —— 具体实现抽象工厂 CoffeeFactory 的工厂子类

对应的关系 UML 图:

image.png

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 咖啡实例

最后运行结果:

image.png

2.2 Good & Bad

Good

  1. 相比起简单工厂,在加入新产品时,都需要修改工厂类;采用工厂模式则可以无需修改抽象工厂和抽象产品的接口,只需要提供一个具体的工厂和具体产品即可,提高了系统的可扩展性。
  2. 满足单一职责原则,工厂父类不再负责判断、创建的一系列工作,只定义好了创建对象的接口。由工厂子类则负责生成具体的产品对象,将实例化的操作延迟至不同的工厂子类中完成。e.g. 中国咖啡厂和日本咖啡厂都是咖啡厂,但是中国咖啡厂可以生产三种咖啡,而日本只能生产两种,这完全取决于各个具体工厂。 Bad
  3. 每个具体工厂只能生产具体的一种产品
  4. 每次新增产品时,都需要编写具体的产品类以及其对应的具体工厂类,系统内的类数会成对增加。

2.3 适用场景

  1. 一个类无需知道其依赖的具体对象类,只需要知道其工厂即可,具体产品对象由具体工厂进行创建。e.g. 业务方需要咖啡这个模糊概念,但是它可以无需关心是具体什么咖啡(Latte or Moca...),只要在需要时,调用某个工厂生成所需的咖啡即可,无须关心是哪一个工厂子类创建产品子类,需要时再动态指定。
  2. 一个工厂父类只需要提供一个创建产品的接口,而由他的具体工厂子类来是确定、实现具体要创建的对象,将创建子类的任务委托给子类工厂。

3. 抽象工厂模式

抽象工厂是工厂模式的进一步推广,抽象。前面提到的工厂模式中,更多考虑的是一类产品的生产,但是显示生活中,一个工厂往往承担不止一个产品的生产。抽象工厂模式中,提供一个创建一系列相关或相互依赖对象的接口,而无须指定它们具体的类,属于对象创建型模式。

故事背景: 当业务扩张之后,一个工厂不局限于生成咖啡了,可能会开始涉猎其他的饮料品类生产,例如茶、苏打水等等,不同工厂主打的方向也不一样,这时候一个咖啡工厂已经不满足实际业务需要了。

Coffee, Tea, Sodas —— 抽象饮料类,咖啡、茶、苏打水

Latte, Moca, Cappuccino ——— 具体咖啡的某一类

PuEr ——— 具体茶的某一类

Cola ——— 具体苏打水的某一类

DrinkingFactory —— 一个抽象的饮料工厂,只定义生产抽象饮料类的接口

ChinaFactory,JanpanFactory —— 具体某个饮料工厂,生产具体的饮料实例

对应的关系 UML 图 (画图偷懒,未一一对应): image.png

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();

最后运行结果: image.png

3.2 Good & Bad

Good

  1. 抽象工厂模式隔离了具体类的生产,调用方不知道什么需要被创建,具体工厂都继承于公共的抽象工厂,实现了抽象工厂定义的公共接口。
  2. 新增具体工厂或者某一族类的产品变得简单,无需修改已有的系统。 Bad
  3. 新增新的族类产品时,难以拓展抽象工厂来生产新的族类,例如再新增了一个八宝粥系列,那就需要修改抽象工厂的内容。支持新的品类则需要对抽象工厂接口作出修改,同时又会涉及其他工厂子类的修改,带来更大的修改风险。
  4. 开闭原则的倾斜性,对于新增工厂、新增产品类目相对简单,但是对于新增新的产品品类则比较麻烦。例如,在茶品类下实现奶茶,相对简单,但是要新增和茶、咖啡、苏打水同一等级的品类时,则相对复杂。
  5. 当只有一个品类时,抽象工厂将会退化为普通的工厂模式,如第二小节所示。

3.3 适用场景

  1. 系统提供一个品类的库,所有的产品以同样的接口出现,从而使调用方无需依赖于具体实现。
  2. 隔离了具体产品类的生成,每次通过具体工厂创建一个品类中的对个类型时比较简单,新增新的工厂或者类目时比较简单。

4. 小结

4.1 简单工厂 & 工厂模式

工厂模式是对简单工厂的进一步推广和抽象,使用了面向对象的多态性,同时保留了简单工厂的优点。在工厂模式中,核心工厂不再负责产品的创建,转而提供创建产品的接口;由工厂子类实现创建细节,将创建产品的时机延迟至子类中实现,将创建过程封装于该类工厂中。这使得工厂模式允许在不修改核心工厂的情况下引入新的产品。

而工厂模式的缺点,通常情况下,产品和工厂是一一对应的关系,于是当引入新的产品时,还要再引入新的生产该产品的工厂类。换言之,在新增产品时,引入类的数量是成对增加的,会提高系统的复杂度。

4.2 工厂模式 & 抽象工厂

抽象工厂是在工厂模式的基础上进一步的拓展。前面提及的工厂模式仅限于同一类产品的生产创建,而抽象工厂面向了不同类产品的生产创建,当系统中只有一个品类时,抽象工厂将退化为工厂模式。

抽象工厂的主要优点是隔离了具体类的生成,使得客户端不需要是什么要被创建,每次只需要通过具体的工厂拿到创建该品类下的产品即可,新增工厂或者新增某个品类下产品变得简单。但是抽象工厂具有带倾斜性的开闭原则支持。当需要新增不同品类时,需要修改的范围包括核心工厂和工厂子类。增加不同品类的会变得复杂。

For more information:

设计模式笔记 Index