在介绍简单工厂模式前,我们先来了解一下构造器模式。
有一天你在的银行要做一个职工录入系统,这个系统开发阶段只有你自己,所以,你说了算。于是,在录入自己的时候,你可以这样写:

var lee = {
  name: "李强",
  age: 26,
  career: "student"
};

录入同事的她时,幸福的笑了,不就是多一个人的事嘛,于是手动添加了:

var mei = {
  name: "梅梅",
  age: 22,
  career: "teacher"
};

突然想到还有很多人员没有录入进来,难道要自己一个个添加吗?幸亏还有构造函数,于是,创建了一个 User 函数:

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

接下来要做的事情就是让程序自动读取数据库里面的所有员工信息,然后对 User 进行一个简单的调用:

var user = new User(name, age, career);

从此你再也不需要手动录入了。
像 User 这种用来初始化对象的的特殊函数,就叫做构造器。在 JavaScript 中,使用构造函数来初始化对象,就是应用了构造器模式。
虽然这个模式很简单,但是我们需要思考一个问题:
在创建 user 过程中,谁变了,谁没变?
很明显,变的是每个 user 的姓名、年龄、职业这些值,这是每个人员的特性,不变的是每个人员都有姓名、年龄、职业这些属性,这是所有人员的共性
那构造器做了什么呢?
构造器不过是将 name、age、career 这些属性的赋值过程进行封装,确保每个对象都具备这些属性,确保共性不变的同时,又对 name、age、career 各自的取值保持了开放。
如果构造模式的本质是抽象不同对象实例之间的变与不变,那么工厂模式的本质就是抽象不同类(构造函数)之间的变与不变。

简单工厂模式

某天行长过来了解情况,提出这个录入系统太简单了,而且行长和员工之间的区别不仅仅需要一个简单的 career 字段,还要有详细的职责说明。也就是至少需要一个描述字段来阐述各自的工作。
你想了想便把 User 拆分成了两个具体的类:

function President(name, age, career, work) {
  this.name = name;
  this.age = age;
  this.career = "president";
  this.work = ["喝茶", "看报纸", "..."];
}
function Employee(name, age, career, work) {
  this.name = name;
  this.age = age;
  this.career = "employees";
  this.work = ["办存款", "放贷款", "收贷款"];
}

目前我们只有两个类暂时可以满足目前行长的需求了,但是麻烦的是:难道每从数据库拿一条数据都要人工判断下这个人员的工种,然后在给它分配构造器吗?不可以的,这个工种也是一个,要把它交给另外一个函数去处理:

function Factory(name, age, career) {
    switch(career) {
        case 'president':
            return new President(name, age);
        case 'employees':
            return new Employee(name, age);
        ...
    }
}

看起来是好了一些,至少不需要人工判断并进行构造器分配了,但是,switch 末尾的省略号是比较恐怖的,全行有好多工种呢,难道我要手动增加这些类和 switch 吗?
当然不是,认真看上面并不完美的代码里,变的是什么?不变的又是什么?
President 和 Employee 两个工种的人员,仍然都有 name、age、career、work 四个属性,只不过是每个字段的值不同,而且 work 的值随着 career 取值不同而改变。这样一分析我们封装的还是不够彻底,共性和个性分离的也不够彻底。
现在我们找回 User 类把相同的逻辑封装进去,然后在 Factory 函数中处理个性的逻辑并把创建的实例对象返回:

function User(name, age, career, work) {
  this.name = name;
  this.age = age;
  this.career = career;
  this.work = work;
}
function Factory(name, age, career) {
    var work;
    switch(career) {
        case 'employees':
            work = ["办存款", "放贷款", "收贷款"];
        case 'president':
            work = ["喝茶", "看报纸", "..."];
        case 'chairman':
            work = ["喝水", "放贷签字", "开会"];
        case xxx:
            // 工种对应职责
        ...
    }
    return new User(name, age, career, work);
}

这样一来需要我们做的事情就简单很多了,不用想着我拿到的数据是什么工种分配什么构造器了,也不需要增加额外的构造器了,而需要我们做的只是传参和扩展了。
工厂模式的简单之处,在于它的概念相对好理解:将创建对象的过程单独封装。同时它的应用场景也非常容易识别:有构造函数的地方,我们就应该想到简单工厂;在写了大量构造函数、调用了大量的 new,自觉非常不爽的情况下,我们就应该思考是不是可以使用工厂模式重构代码了。

源码中的简单工厂模式

  • vue-router 源码中的简单工厂模式

代码位置:vue-router/src/index.js

// src/index.js
export default class VueRouter {
    constructor(options) {
        this.mode = mode // 路由模式
        
        switch (mode) { // 简单工厂
            case 'history': // history 方式
                this.history = new HTML5History(this, options.base)
                break
            case 'hash': // hash 方式
                this.history = new HashHistory(this, options.base, this.fallback)
                break
            case 'abstract': // abstract 方式 代表非浏览器环境中路由方式
                this.history = new AbstractHistory(this, options.base)
                break
            default:
                // ... 初始化失败报错
        }
    }
}

源码里没有把工厂方法的产品创建流程封装出来,而是直接将产品实例的创建流程暴露在 VueRouter 的构造函数中,在被 new 的时候创建对应产品实例,相当于 VueRouter 的构造函数就是一个工厂方法。

如果一个系统不是 SPA (Single Page Application,单页应用),而是是 MPA(Multi Page Application,多页应用),那么就需要创建多个 VueRouter 的实例,此时 VueRouter 的构造函数也就是工厂方法将会被多次执行,以分别获得不同实例。


杜子李_
0 声望0 粉丝