设计模式的学习之道--工厂模式

80 阅读6分钟

工厂模式

在介绍⼯⼚模式之前,为了辅助⼤家的理解,我想先在这⼉给⼤家介绍⼀下构造器模式。

有⼀天你写了个公司员⼯信息录⼊系统,这个系统开发阶段⽤户只有你⾃⼰,想怎么玩怎么玩。于是在创建“⾃⼰”这个唯⼀的⽤户的时候,你可以这么写:

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

有⼀天你的同桌韩梅梅突然说:“李雷,让我瞅瞅你的系统做得咋样了,我也想被录进去”。你说好,不就多⼀个⼈的事情吗,于是代码⾥⼿动多了⼀个韩梅梅:

const liLei = {
    name: '李雷',
    age: 25,
    career: 'coder',
}
const hanMeiMei = {
    name: '韩梅梅',
    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这样当新建对象的内存被分配后,⽤来初始化该对象的特殊函数,就叫做构造器。在JavaScript中我们使⽤构造函数去初始化对象,就是应⽤了构造器模式。这个模式太简单了,简单到我这⼀通讲对很多同学来说其实并不必要,⼤家都是学过 JavaScript 基础的⼈,都知道怎么new⼀个对象。但是我们洋洋洒洒这么⼀段的⽬的,并不是为了带⼤家复习构造函数本身的⽤法,⽽是希望⼤家去思考开篇我们提到的问题:

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

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

那么构造器做了什么?

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

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

简单⼯⼚模式

咱们先不说简单⼯⼚模式定义是啥,咱们先来看李雷的新需求:

⽼板说这个系统录⼊的信息也太简单了,程序员和产品经理之间的区别⼀个简单的 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 的末尾写了个省略号,这个省略号⽐较恐怖。看着这个省略号,李雷哭了,他想到:整个公司上下有数⼗个⼯种,难道我要⼿写数⼗个类、数⼗⾏ 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、⾃觉⾮常不爽的情况下,我们就应该思考是不是可以掏出⼯⼚模式重构我们的代码了。