简单工厂模式(创建型)
简单工厂 —— 区分“变与不变”
一、构造器
这里用个例子来介绍一下构造器模式:
有一天,我写了个员工信息录入系统,起初这个系统开发阶段用户只有我,想怎么玩怎么玩。
于是在创建我这个唯一的用户时,就可以这么写:
const li = {
name: "Leophen",
age: 23,
career: "coder",
};
有一天我的同桌 Tim 也想被录入系统,于是我的代码里手动多了一个 Tim:
const li = {
name: "Leophen",
age: 23,
career: "coder",
};
const may = {
name: "Tim",
age: 24,
career: "product manager",
};
过了两天老板过来了,想把部门的 500 人录入看看功能。
我心想,500 个对象字面量,要死要死,还好有构造函数,于是写出了一个可以自动创建用户的 User
函数:
function User(name, age, career) {
this.name = name;
this.age = age;
this.career = career;
}
上面这个 User
,就是一个构造器。
这里采用了 ES5 构造函数的写法,因为 ES6 中的 Class 其实本质上还是函数,Class
语法只是语法糖,构造函数才是它的真面目。
接下来要做的,就是让程序自动读取数据库中一行行的员工信息,然后把拿到的姓名、年龄等字段塞进 User
函数中,进行一个简单的调用:
const user = new User(name, age, career)
到这里,就再不用手写字面量了。
像 User
这样当新建对象的内存被分配后,用来初始化该对象的特殊函数,就叫做构造器。
在 JS 中,我们使用构造函数去初始化对象,就是应用了构造器模式。
这里引出一个问题:
在创建一个 user
过程中,谁变了,谁不变?
很明显,变的是每个 user
的姓名、年龄、工种这些值,这是用户的个性,不变的是每个员工都具备姓名、年龄、工种这些属性,这是用户的共性。
构造器就是将 name
、age
、career
赋值给对象的过程封装,确保了每个对象都具备这些属性,确保了共性的不变,同时将 name
、age
、career
各自的取值操作开放,确保了个性的灵活。
如果使用构造器模式时,我们本质上是去抽象了每个对象实例的变与不变。那么使用工厂模式时,我们要做的就是去抽象不同构造函数(类)之间的变与不变。
二、简单工厂模式
举个例子:
老板还想要这个系统具备给不同工种分配职责说明的功能。
也就是说,要给每个工种的用户加上一个特定的字段,来描述他们的工作内容,这下员工的共性被拆离了。
我心想不就是多写个构造器的事儿吗,于是:
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", "催更"];
}
现在已经有两个类(后面可能还会有更多的类),麻烦的事情来了:难道每从数据库拿到一条数据,都要人工判断一下这个员工的工种,然后手动给它分配构造器吗?
不行,这也是一个“变”,可以把这个“变”交给一个函数去处理:
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
吗?
当然不!
回到最初的问题:上面代码中,变的是什么?不变的又是什么?
Coder
和 ProductManager
两个工种的员工,仍然存在 name
、age
、career
、work
等属性的共性。它们的区别在于每个字段取值的不同,以及 work
字段需要随 career
字段取值的不同而改变。
这样一看,是不是对共性封装得不够彻底?那么相应地,共性与个性是不是分离得也不够彻底?
现在把相同的逻辑封装回 User
类里,然后把这个承载了共性的 User
类和个性化的逻辑判断写入同一个函数:
function User(name, age, career, work) {
this.name = name
this.age = age
this.career = career
this.work = work
}
function Factory(name, age, career) {
let work
switch (career) {
case 'coder':
work = ['写代码', '修Bug']
break
case 'product manager':
work = ['写PRD', '催更']
break
case 'boss':
work = ['喝茶', '看报']
case 'xxx':
// 其它工种的职责分配
...
return new User(name, age, career, work)
}
这样一来,要做事情就简单很多了。
不用时刻想着拿到的这组数据是什么工种、应该怎么给它分配构造函数,更不用手写无数个构造函数——Factory 已经帮我们做完了一切,而我们只需像以前一样无脑传参就可以了!
现在总结一下什么是工厂模式:
工厂模式其实就是将创建对象的过程单独封装。
它很像我们去餐馆点菜:比如说点一份西红柿炒蛋,我们不用关心西红柿怎么切、怎么打鸡蛋这些菜品制作过程中的问题,我们只关心摆上桌那道菜。
在工厂模式中,传参就是点菜,工厂函数中运转的逻辑就相当于炒菜——这部分工作我们不用关心,我们只要能拿到工厂交付给我们的实例结果就行了。
三、总结
工厂模式就是将创建对象的过程单独封装。
有构造函数的地方,就应该想到用简单工厂;在写了大量构造函数、调用了大量的 new
时,就应该思考是不是可以掏出工厂模式重构代码了。