设计模式(二)——创建型:工厂模式-区分变与不变

·

2 min read

设计模式(二)——创建型:工厂模式-区分变与不变

浅谈构造器

构造器代码示例:

class User {
  name: string;
  age: number;
  carrer: string;

  constructor(name: string, age: number, career: string) {
    this.name = name;
    this.age = age;
    this.carrer = career;
  }
}

const user = new User("suxiong", 24, "font-end developer");

我们在使用构造器模式时,本质上是抽象了每个对象实例的变与不变——确保了name,age,carrer属性共性的不变,同时将其各自的取值操作开放,保证了个性的灵活。那么,我们使用工厂模式时,我们要做的就是去抽象不同构造函数(类)之间的变与不变。

简单工厂模式

对于不同的工种,具有不同的职能。使用构造器模式的话,我们需要创建多个不同工种的构造函数。

class Coder {
  name: string;
  age: number;
  carrer: string = ‘font-end developer’;
  word: string[] = ["code", "fix bug"];

  constructor(name: string, age: number) {
    this.name = name;
    this.age = age;
    this.carrer = career;
  }
}
class Pm {
  name: string;
  age: number;
  carrer: string = 'pm';
  word: string[] = ["book meeting", "write prd"];

  constructor(name: string, age: number) {
    this.name = name;
    this.age = age;
    this.carrer = career;
  }
}

但工种变多的话,我们就需要写数十个类,很明显是不可取的。我们可以将相同的逻辑封装回User类中,把这个承载了共性的User类和个性化的逻辑判断写入同一个函数。

class User {
  name: string;
  age: number;
  carrer: string;
  work: string[];

  constructor(name: string, age: number, career: string, work: string[]) {
    this.name = name;
    this.age = age;
    this.carrer = career;
    this.work = work;
  }
}

class UserFactory {
  work: string[];
  constructor(name: string, age: number, career: string) {
    switch (career) {
      case "font-end developer":
        this.work = ["code", "fix bug"];
        break;
      case "product manager":
        this.work = ["book meeting", "write prd"];
        break;
      default:
        this.work = [];
        break;
    }
    return new User(name, age, career, this.work);
  }
}

所谓工厂模式其实就是将创建对象的过程单独封装,它很像我们去餐馆点菜——不需要关心西红柿怎么切,我们只关心摆上桌那道菜。因此,工厂模式的目的就是为了实现无脑传参。

其实上述代码有一个问题,若是career有若干种,那么Factory会变得异常庞大,后期维护异常艰难。

  1. 坑死了队友。Factory的逻辑过于繁杂和混乱,没人敢维护它。
  2. 坑死了测试。每次新增一个career,对方都不得不对整个Factory的逻辑进行回归。

一切悲剧的根源是其未遵守开放封闭原则——对拓展开放,对修改封闭。说得更准确点,软件实体(类、模块、函数)可以扩展,但是不可修改。

抽象工厂模式

我们可以用一个抽象类来声明一个实例的基本属性,用具体类来声明该实例的特定属性。

class User {
  name: string;
  age: number;

  constructor(name: string, age: number) {
    this.name = name;
    this.age = age;
  }

  createCoder() {
    return new Coder();
  }
}

class Career {
  getCareerName() {
    console.log("I have a career.");
  }
}

class Coder extends Career {
  code() {
    console.log(" I'm coding now!");
  }
}

const people = new User("suxiong", 24);
const coder = people.createCoder();
coder.code()

通过这样,我们可以保证抽象类的不变,通过具体类对抽象类进行扩展。我们可以对类的性质进行划分。

  • 抽象工厂(抽象类,不能被用于生成具体实例):用于声明最终目标产品的共性。在一个系统中,抽象工厂可以有多个,每一个抽象工厂对应的这一类的产品。
  • 具体工厂(用于生成产品族里的一个具体产品):继承自抽象工厂、实现了抽象工厂里声明的那些方法,用于创建具体的产品的类。
  • 抽象产品(抽象类,它不能被用于生成具体实例)
  • 具体产品(用于生成产品族里的一个具体的产品所依赖的更细粒度的产品)

抽象工厂模式是围绕一个超级工厂创建其他工厂,用于解决多个工厂的变与不变的问题。

浅谈学习

有人会问——抽象工厂对我们而言有什么价值?这么一个看似鸡肋、其实也确实不怎么常用的一个设计模式,凭什么值得我们花费这么大力气去理解他?

  1. 前端工程师首先是软件工程师,只通过JavaScript去理解软件世界,是一件可怕的事情,它会窄化技术视野——因为JavaScript只是编程语言的一个分支,虽说它很流行,但它还不够强大。
  2. 在今后的职业生涯里,可能不止一次遇到服务端/客户端出身、或者单纯对面试者知识广度有疯狂执念的不同背景不同脑回路的面试官。在他们的世界里,不知道抽象工厂就像不知道this一样恐怖。
  3. 设计模式的术其实是在佐证它的道,抽象工厂是佐证开放封闭原则的良好素材。

技术,尤其是前端技术,它的更新迭代速度是非常快的。小看那些看似“无用”的知识,会极大地限制自己能力和职业生涯的可能性。举个例子,React新版本推出的Fiber架构现在很火,很多同学认为这是个特别新潮的玩意儿。但是它并不新潮,作为一种架构模式,它在软件领域早就有过不同姿势的生产实践,React并不是Fiber的发明者,而是Fiber的使用者和受益者。