Skip to content

创建型-简单工厂

该小节需要注意理解区分 变与不变

先来说说构造器

在介绍工厂模式之前,为了辅助大家的理解,我想先在这儿给大家介绍一下构造器模式(这东西很常见)。

以下是个业务场景:某天你写了个 公司员工信息录入系统 , 这个系统开发阶段用户只有你自己,想怎么玩怎么玩。于是在创建“自己”这个唯一的用户的时候,你可以这么写:

js
const liLei = {
  name: '李雷',
  age: 25,
  career: 'coder',
}

某天某个同事说,我也要录入进去,于是手动在代码中录入了该同事的信息

js
const liLei = {
  name: '李雷',
  age: 25,
  career: 'coder',
}

const hanMeiMei = {
  name: '韩梅梅',
  age: 24,
  career: 'product manager'
}

有过了两天,你老板说,需要将公司500人的信息都录入系统,面对500人的字面变量,手动添加要死人,于是,写出了一个可以自动创建用户的 User 函数:

js
function User(name , age, career) {
  this.name = name
  this.age = age
  this.career = career 
}

// ES6 面向对象语法糖
// class User {
//   constructor(name, age, career) {
//     this.name = name
//     this.age = age
//     this.career = career
//   }
// }

上述代码中的 User 其实就是一个构造器,此处是ES5的标准写法。因为 ES6 中的 class 其实本质上还是函数,class 语法只是语法糖,构造函数,才是它的真面目。

接下来要做的事情,就是让程序自动地去读取数据库里面一行行的员工信息,然后把拿到的姓名、年龄等字段塞进User函数里,进行一个简单的调用:

js
const user = new User(name, age, career);

User 这样当新建对象的内存被分配后,用来初始化该对象的特殊函数,就叫做构造器。在 JavaScript 中,我们使用构造函数去初始化对象,就是应用了构造器模式。在此,不是复议构造器。

在创建一个user过程中,谁变了,谁不变?

很明显,变的是每个user的姓名、年龄、工种这些值,这是用户的个性,不变的是每个员工都具备姓名、年龄、工种这些属性,这是用户的共性。

构造器 在这里做了什么呢?构造器是不是将 name、age、career 赋值给对象的过程封装,确保了每个对象都具备这些属性,确保了共性的不变,同时将 name、age、career 各自的取值操作开放,确保了个性的灵活?

如果在使用构造器模式的时候,我们本质上是去抽象了每个对象实例的变与不变。那么使用工厂模式时,我们要做的就是去抽象不同构造函数(类)之间的变与不变。

简单工厂模式

在上述的业务场景上继续提新的需求:程序员和产品经理之间的区别一个简单的career字段怎么能说得清?我要求这个系统具备给不同工种分配职责说明的功能。也就是说,要给每个工种的用户加上一个个性化的字段,来描述他们的工作内容。

此时,员工的共性被拆离了。所以按照目前的需求,继续多写连个构造器:

js
// 程序员
function Coder(name , age) {
    this.name = name
    this.age = age
    this.career = 'coder' 
    this.work = ['写代码','写系分', '修Bug']
}

// 产品经理
function ProductManager(name, age) {
    this.name = name 
    this.age = age
    this.career = 'product manager'
    this.work = ['订会议室', '写PRD', '催更']
}

现在我们有两个类(后面可能还会有更多的类),麻烦的事情来了:难道我每从数据库拿到一条数据,都要人工判断一下这个员工的工种,然后手动给它分配构造器吗?不行,这也是一个“变”,我们把这个“变”交给一个函数去处理:

js
function Factory(name, age, career) {
  switch(career) {
    case 'coder':
      return new Coder(name, age) 
      break
    case 'product manager':
      return new ProductManager(name, age)
      break
    ...
}

看起来是好一些了,至少我们不用操心构造函数的分配问题了。但是大家注意我在 switch 的末尾写了个省略号,这个省略号比较恐怖。看着这个省略号,李雷哭了,他想到:整个公司上下有数十个工种,难道我要手写数十个类、数十行 switch 吗?

当然不!回到我们最初的问题:大家仔细想想,在楼上这两段并不那么好的代码里,变的是什么?不变的又是什么?

Coder 和 ProductManager 两个工种的员工,是不是仍然存在都拥有 name、age、career、work 这四个属性这样的共性?它们之间的区别,在于每个字段取值的不同,以及 work 字段需要随 career 字段取值的不同而改变。这样一来,我们是不是对共性封装得不够彻底?那么相应地,共性与个性是不是分离得也不够彻底?

现在我们把相同的逻辑封装回User类里,然后把这个承载了共性的 User 类和个性化的逻辑判断写入同一个函数:

js
const User = function(name, age, career, work) {
  this.name = name;
  this.age = age;
  this.career = career;
  this.work = work;
}

const Factory(name, age, career) {
  let work;
  switch(career) {
    case 'coder':
      work = ['写代码', '写文档', '写单元测试'];
      break;
    case 'product manager':
      work = ['写PRD', '写PRD文档', '写PRD单元测试'];
      break;
    case 'boss':
      work = ['喝茶', '看报', '见客户'];
      break;
    case 'xxx':
      // 其它工种的职责分配
      ...
    }
  }
  return new User(name, age, career, work);
}

这样一来,我们要做事情是不是简单太多了?不用自己时刻想着我拿到的这组数据是什么工种、我应该怎么给它分配构造函数,更不用手写无数个构造函数——Factory已经帮我们做完了一切,而我们只需要像以前一样无脑传参就可以了!

小结

工厂模式的简单之处,在于它的概念相对好理解:将创建对象的过程单独封装,这样的操作就是工厂模式。同时它的应用场景也非常容易识别:有构造函数的地方,我们就应该想到简单工厂;在写了大量构造函数、调用了大量的 new、自觉非常不爽的情况下,我们就应该思考是不是可以掏出工厂模式重构我们的代码了。