说说你对工厂模式的理解

145 阅读11分钟

工厂模式是什么?

工厂模式是用来创建对象的一种最常用的设计模式,不暴露创建对象的具体逻辑,而是将将逻辑封装在一个函数中,那么这个函数就可以被视为一个工厂

其就像工厂一样重复的产生类似的产品,工厂模式只需要我们传入正确的参数,就能生产类似的产品

举个例子:

  1. 编程中,在一个 A 类中通过 new 的方式实例化了类 B,那么 A 类和 B 类之间就存在关联(耦合)
  2. 后期因为需要修改了 B 类的代码和使用方式,比如构造函数中传入参数,那么 A 类也要跟着修改,一个类的依赖可能影响不大,但若有多个类依赖了 B 类,那么这个工作量将会相当的大,容易出现修改错误,也会产生很多的重复代码,这无疑是件非常痛苦的事;
  3. 这种情况下,就需要将创建实例的工作从调用方(A类)中分离,与调用方解耦,也就是使用工厂方法创建实例的工作封装起来(减少代码重复),由工厂管理对象的创建逻辑,调用方不需要知道具体的创建过程,只管使用,而降低调用者因为创建逻辑导致的错误

工厂模式如何实现?

工厂模式根据抽象程度的不同可以分为:

  • 简单工厂模式(Simple Factory)
  • 工厂方法模式(Factory Method)
  • 抽象工厂模式(Abstract Factory)

简单工厂模式

简单工厂模式也叫静态工厂模式,用一个工厂对象创建同一类对象类的实例

假设我们要开发一个公司岗位及其工作内容的录入信息,不同岗位的工作内容不一致

ES5

function Factory(career) {
    function User(career, work) {
        this.career = career 
        this.work = work
    }
    let work
    switch(career) {
        case 'coder':
            work =  ['写代码', '修Bug'] 
            return new User(career, work)
            break
        case 'hr':
            work = ['招聘', '员工信息管理']
            return new User(career, work)
            break
        case 'driver':
            work = ['开车']
            return new User(career, work)
            break
        case 'boss':
            work = ['喝茶', '开会', '审批文件']
            return new User(career, work)
            break
    }
}
let coder = new Factory('coder')
console.log(coder)
let boss = new Factory('boss')
console.log(boss)

Factory就是一个简单工厂。当我们调用工厂函数时,只需要传递name、age、career就可以获取到包含用户工作内容的实例对象

ES6

// 定义用户类
class User {
  constructor(career,work) {
    this.career = career;
    this.work = work;
  }

  getName() {
    return this.career;
  }
}

// 定义工厂类
class Factory {
  createProduct(career) {
    switch(career) {
        case 'coder':
            work =  ['写代码', '修Bug'] 
            return new User(career, work)
            break
        case 'hr':
            work = ['招聘', '员工信息管理']
            return new User(career, work)
            break
        case 'driver':
            work = ['开车']
            return new User(career, work)
            break
        case 'boss':
            work = ['喝茶', '开会', '审批文件']
            return new User(career, work)
            break
    }
  }
}

// 使用工厂创建产品
const factory = new Factory();
const coder = factory.createProduct('coder');
const boss = factory.createProduct('boss');

console.log(product1.getName()); // 输出:coder
console.log(product2.getName()); // 输出:boss

工厂方法模式

工厂方法模式跟简单工厂模式差不多,但是把具体的产品放到了工厂函数的prototype

这样一来,扩展产品种类就不必修改工厂函数了,核心类就变成抽象类,也可以随时重写某种具体的产品

也就是相当于工厂总部不生产产品了,交给下辖分工厂进行生产;但是进入工厂之前,需要有个判断来验证你要生产的东西是否是属于我们工厂所生产范围,如果是,就丢给下辖工厂来进行生产

ES5

// 工厂方法
function Factory(career){
    if(this instanceof Factory){
        var a = new this[career]();
        return a;
    }else{
        return new Factory(career);
    }
}
// 工厂方法函数的原型中设置所有对象的构造函数
Factory.prototype = {
    'coder': function(){
        this.careerName = '程序员'
        this.work = ['写代码', '修Bug'] 
    },
    'hr': function(){
        this.careerName = 'HR'
        this.work = ['招聘', '员工信息管理']
    },
    'driver': function () {
        this.careerName = '司机'
        this.work = ['开车']
    },
    'boss': function(){
        this.careerName = '老板'
        this.work = ['喝茶', '开会', '审批文件']
    }
}
let coder = new Factory('coder')
console.log(coder)
let hr = new Factory('hr')
console.log(hr)

ES6

// 定义用户接口
class User {
  getName() {}
}

// 定义具体用户类 coder
class Coder extends Product {
  getName() {
    return 'coder';
  }
}
// 定义具体用户类 hr
class Hr extends Product {
  getName() {
    return 'hr';
  }
}
// 定义具体用户类 driver
class Driver extends Product {
  getName() {
    return 'driver';
  }
}
// 定义具体用户类 boss
class Boss extends Product {
  getName() {
    return 'boss';
  }
}

// 定义工厂接口
class Factory {
  createProduct() {}
}

// 定义具体工厂类 Coder,生产 CreteFactoryCoder 实例
class CreteFactoryCoder extends Factory {
  createProduct() {
    return new Coder();
  }
}
// 定义具体工厂类 Hr,生产 CreteFactoryHr 实例
class CreteFactoryHr extends Factory {
  createProduct() {
    return new Hr();
  }
}

// 使用工厂方法创建产品
const coder = new CreteFactoryCoder().createProduct();
const hr = new CreteFactoryHr().createProduct();

抽象工厂模式

上述简单工厂模式和工厂方法模式都是直接生成实例,但是抽象工厂模式不同,抽象工厂模式并不直接生成实例, 而是用于对产品类簇的创建

通俗点来讲就是:简单工厂和工厂方法模式的工作是生产产品,那么抽象工厂模式的工作就是生产工厂的

由于JavaScript中并没有抽象类的概念,只能模拟,可以分成四部分:

  • 用于创建抽象类的函数
  • 抽象类
  • 具体类
  • 实例化具体类

ES5

// 定义产品接口
// 相当于说明有两类产品(A、B)
function ProductA() {}
ProductA.prototype.getName = function() {}

function ProductB() {}
ProductB.prototype.getName = function() {}

// 定义具体产品类 A1
// 相当于说明A类有A1、A2
function ConcreteProductA1() {}
ConcreteProductA1.prototype = Object.create(ProductA.prototype);
ConcreteProductA1.prototype.getName = function() {
  return 'ConcreteProductA1';
}

// 定义具体产品类 A2
function ConcreteProductA2() {}
ConcreteProductA2.prototype = Object.create(ProductA.prototype);
ConcreteProductA2.prototype.getName = function() {
  return 'ConcreteProductA2';
}

// 定义具体产品类 B1
// 相当于说明B类有B1、B2
function ConcreteProductB1() {}
ConcreteProductB1.prototype = Object.create(ProductB.prototype);
ConcreteProductB1.prototype.getName = function() {
  return 'ConcreteProductB1';
}

// 定义具体产品类 B2
function ConcreteProductB2() {}
ConcreteProductB2.prototype = Object.create(ProductB.prototype);
ConcreteProductB2.prototype.getName = function() {
  return 'ConcreteProductB2';
}

// 定义抽象工厂接口
// 相当于说明有两个工厂(A、B)
function AbstractFactory() {
  // 此处可以优化
}
AbstractFactory.prototype.createProductA = function() {}
AbstractFactory.prototype.createProductB = function() {}

// 定义具体工厂类 1,生产 ConcreteProductA1 和 ConcreteProductB1 实例
// 相当于说明A工厂能生产A1、B1
function ConcreteFactory1() {}
ConcreteFactory1.prototype = Object.create(AbstractFactory.prototype);
ConcreteFactory1.prototype.createProductA = function() {
  return new ConcreteProductA1();
}
ConcreteFactory1.prototype.createProductB = function() {
  return new ConcreteProductB1();
}

// 定义具体工厂类 2,生产 ConcreteProductA2 和 ConcreteProductB2 实例
// 相当于 B工厂能生产A2、B2
function ConcreteFactory2() {}
ConcreteFactory2.prototype = Object.create(AbstractFactory.prototype);
ConcreteFactory2.prototype.createProductA = function() {
  return new ConcreteProductA2();
}
ConcreteFactory2.prototype.createProductB = function() {
  return new ConcreteProductB2();
}

// 使用抽象工厂创建产品
// 相当于 开始生产
var factory1 = new ConcreteFactory1();
var productA1 = factory1.createProductA();
var productB1 = factory1.createProductB();
console.log(productA1.getName()); // 输出:ConcreteProductA1
console.log(productB1.getName()); // 输出:ConcreteProductB1

var factory2 = new ConcreteFactory2();
var productA2 = factory2.createProductA();
var productB2 = factory2.createProductB();
console.log(productA2.getName()); // 输出:ConcreteProductA2
console.log(productB2.getName()); // 输出:ConcreteProductB2

ES6

// 定义产品接口
class ProductA {
  getName() {}
}

class ProductB {
  getName() {}
}

// 定义具体产品类 A1
class ConcreteProductA1 extends ProductA {
  getName() {
    return 'ConcreteProductA1';
  }
}

// 定义具体产品类 A2
class ConcreteProductA2 extends ProductA {
  getName() {
    return 'ConcreteProductA2';
  }
}

// 定义具体产品类 B1
class ConcreteProductB1 extends ProductB {
  getName() {
    return 'ConcreteProductB1';
  }
}

// 定义具体产品类 B2
class ConcreteProductB2 extends ProductB {
  getName() {
    return 'ConcreteProductB2';
  }
}

// 定义抽象工厂接口
class AbstractFactory {
  // 此处可以优化
  createProductA() {}
  createProductB() {}
}

// 定义具体工厂类 1,生产 ConcreteProductA1 和 ConcreteProductB1 实例
class ConcreteFactory1 extends AbstractFactory {
  createProductA() {
    return new ConcreteProductA1();
  }
  createProductB() {
    return new ConcreteProductB1();
  }
}

// 定义具体工厂类 2,生产 ConcreteProductA2 和 ConcreteProductB2 实例
class ConcreteFactory2 extends AbstractFactory {
  createProductA() {
    return new ConcreteProductA2();
  }
  createProductB() {
    return new ConcreteProductB2();
  }
}

// 使用抽象工厂创建产品
const factory1 = new ConcreteFactory1();
const productA1 = factory1.createProductA();
const productB1 = factory1.createProductB();
console.log(productA1.getName()); // 输出:ConcreteProductA1
console.log(productB1.getName()); // 输出:ConcreteProductB1

const factory2 = new ConcreteFactory2();
const productA2 = factory2.createProductA();
const productB2 = factory2.createProductB();
console.log(productA2.getName()); // 输出:ConcreteProductA2
console.log(productB2.getName()); // 输出:ConcreteProductB2

抽象工厂的优化

定义抽象工厂接口时内部实现可以适当的增加判断,缓存等优化

let AbstractFactory = function(subType, superType) {
  // 判断抽象工厂中是否有该抽象类
  if (typeof CareerAbstractFactory[superType] === 'function') {
    // 缓存类
    function F() {}
    // 继承父类属性和方法
    F.prototype = new CareerAbstractFactory[superType]()
    // 将子类的constructor指向父类
    subType.constructor = subType;
    // 子类原型继承父类
    subType.prototype = new F()
  } else {
    throw new Error('抽象类不存在')
  }
}

工厂模式的应用场景?

从上面可看到,简单简单工厂的优点就是我们只要传递正确的参数,就能获得所需的对象,而不需要关心其创建的具体细节。

应用场景也容易识别,有构造函数的地方,就应该考虑简单工厂,但是如果函数构建函数太多与复杂,会导致工厂函数变得复杂,所以不适合复杂的情况。

抽象工厂模式一般用于严格要求以面向对象思想进行开发的超大型项目中,我们一般常规的开发的话一般就是简单工厂和工厂方法模式会用的比较多一些

综上,工厂模式适用场景如下:

  • 如果你不想让某个子系统与较大的那个对象之间形成强耦合,而是想运行时从许多子系统中进行挑选的话,那么工厂模式是一个理想的选择
  • 将new操作简单封装,遇到new的时候就应该考虑是否用工厂模式;
  • 需要依赖具体环境创建不同实例,这些实例都有相同的行为,这时候我们可以使用工厂模式,简化实现的过程,同时也可以减少每种对象所需的代码量,有利于消除对象间的耦合,提供更大的灵活性

应用实例

  • jQuery的$(selector)很显然直接$()很方便 ,这是因为$()已经是一个工厂方法了;

    class jQuery {
        constructor(selector) {
            super(selector)
        }
        //  ....
    }
    
    window.$ = function(selector) {
        return new jQuery(selector)
    }
    
  • React的createElement()

    React.createElement()方法就是一个工厂方法

    var profile = React.createElement(
      "div",
      null,
      React.createElement("img",{src:"avatar.png",className:"profile"}),
      React.createElement("h3",{className:"profile"})
    )
    
    //源码
    class Vnode(tag,attrs,chilren){
      //...省略内部代码
    }
    
    React.createElement = function (tag,attrs,children){
      return new Vnode(tag,attrs,chilren)
    }
    
  • Vue的异步组件

    通过promise的方式resolve出来一个组件

    Vue.component('async-example',function (resolve,reject){
      setTimeout(function (){
        resolve({
          template:'<div>I am async!</div>'
        })
    	},1000)
    }
    

项目实战

权限管理

实现权限管理的方式有很多种,其中按角色添加权限可以应用到工厂模式。

以普通的vue + vue-router的项目为例,我们通常将所有的路由写入到router/index.js这个文件中。

// index.js

import Vue from 'vue'
import Router from 'vue-router'
import Login from '../components/Login.vue'
import SuperAdmin from '../components/SuperAdmin.vue'
import NormalAdmin from '../components/Admin.vue'
import User from '../components/User.vue'
import NotFound404 from '../components/404.vue'

Vue.use(Router)

export default new Router({
  routes: [
    //重定向到登录页
    {
      path: '/',
      redirect: '/login'
    },
    //登陆页
    {
      path: '/login',
      name: 'Login',
      component: Login
    },
    //超级管理员页面
    {
      path: '/super-admin',
      name: 'SuperAdmin',
      component: SuperAdmin
    },
    //普通管理员页面
    {
      path: '/normal-admin',
      name: 'NormalAdmin',
      component: NormalAdmin
    },
    //普通用户页面
    {
      path: '/user',
      name: 'User',
      component: User
    },
    //404页面
    {
      path: '*',
      name: 'NotFound404',
      component: NotFound404
    }
  ]
})

当涉及权限管理页面的时候,通常需要在用户登陆根据权限开放固定的访问页面并进行相应权限的页面跳转。但是如果我们还是按照老办法将所有的路由写入到router/index.js这个文件中,那么低权限的用户如果知道高权限路由时,可以通过在浏览器上输入url跳转到高权限的页面。所以我们必须在登陆的时候根据权限使用vue-router提供的addRoutes方法给予用户相对应的路由权限。这个时候就可以使用简单工厂方法来改造上面的代码。

router/index.js文件中,我们只提供/login这一个路由页面。

//index.js

import Vue from 'vue'
import Router from 'vue-router'
import Login from '../components/Login.vue'

Vue.use(Router)

export default new Router({
  routes: [
    //重定向到登录页
    {
      path: '/',
      redirect: '/login'
    },
    //登陆页
    {
      path: '/login',
      name: 'Login',
      component: Login
    }
  ]
})

我们在router/文件夹下新建一个routerFactory.js文件,导出routerFactory简单工厂函数,用于根据用户权限提供路由权限,代码如下

//routerFactory.js

import SuperAdmin from '../components/SuperAdmin.vue'
import NormalAdmin from '../components/Admin.vue'
import User from '../components/User.vue'
import NotFound404 from '../components/404.vue'

let AllRoute = [
  //超级管理员页面
  {
    path: '/super-admin',
    name: 'SuperAdmin',
    component: SuperAdmin
  },
  //普通管理员页面
  {
    path: '/normal-admin',
    name: 'NormalAdmin',
    component: NormalAdmin
  },
  //普通用户页面
  {
    path: '/user',
    name: 'User',
    component: User
  },
  //404页面
  {
    path: '*',
    name: 'NotFound404',
    component: NotFound404
  }
]

let routerFactory = (role) => {
  switch (role) {
    case 'superAdmin':
      return {
        name: 'SuperAdmin',
        route: AllRoute
      };
      break;
    case 'normalAdmin':
      return {
        name: 'NormalAdmin',
        route: AllRoute.splice(1)
      }
      break;
    case 'user':
      return {
        name: 'User',
        route:  AllRoute.splice(2)
      }
      break;
    default: 
      throw new Error('参数错误! 可选参数: superAdmin, normalAdmin, user')
  }
}

export { routerFactory }

在登陆页导入该方法,请求登陆接口后根据权限添加路由:

//Login.vue

import {routerFactory} from '../router/routerFactory.js'
export default {
  //... 
  methods: {
    userLogin() {
      //请求登陆接口, 获取用户权限, 根据权限调用this.getRoute方法
      //..
    },
    
    getRoute(role) {
      //根据权限调用routerFactory方法
      let routerObj = routerFactory(role);
      
      //给vue-router添加该权限所拥有的路由页面
      this.$router.addRoutes(routerObj.route);
      
      //跳转到相应页面
      this.$router.push({name: routerObj.name})
    }
  }
};

在实际项目中,因为使用this.$router.addRoutes方法添加的路由刷新后不能保存,所以会导致路由无法访问。通常的做法是本地加密保存用户信息,在刷新后获取本地权限并解密,根据权限重新添加路由。这里因为和工厂模式没有太大的关系就不再赘述。

总结

  • 简单工厂模式又叫静态工厂方法,用来创建某一种产品对象的实例,用来创建单一对象;
  • 工厂方法模式是将创建实例推迟到子类中进行;
  • 抽象工厂模式是对类的工厂抽象用来创建产品类簇,不负责创建某一类产品的实例。

在实际的业务中,需要根据实际的业务复杂度来选择合适的模式。对于非大型的前端应用来说,灵活使用简单工厂其实就能解决大部分问题。

优缺点

优:

  • 创建对象过程可能很复杂,但我们只需要关心创建结果
  • 构造函数和创建者分离,符合“开闭原则”
  • 一个调用者想创建一个对象,只要知道其名称就可以了。
  • 扩展性高,如果想增加一个产品,只要扩展一个工厂类就可以。

缺:

  • 添加新产品时,需要编写新的具体产品类,一定程度上增加了系统的复杂度
  • 考虑到系统的可扩展性,需要引入抽象层,在客户端代码中均使用抽象层进行定义,增加了系统的抽象性和理解难度


最后一句
学习心得!若有不正,还望斧正。希望掘友们不要吝啬对我的建议。