工厂模式

85 阅读3分钟

一起养成写作习惯!这是我参与「掘金日新计划 · 4 月更文挑战」的第14天,点击查看活动详情

工厂模式介绍

工厂模式 (Factory Pattern),根据不同的输入, 返回不同类的实例,一般用来创建同一类对象。工厂方式的主要思想是将对象的创建与对象的实现分离。

举个通俗的例子,比如你要去外面吃饭,到饭店了跟老板说要两个菜,一个鱼香肉丝一个宫保鸡丁。等一会后菜就给你端到了面前,你不用管菜烧出来的过程,你只要负责吃就行了。

这个例子中,饭店老板相当于工厂函数,你需要吃菜的时候直接找老板生产就行。

工厂模式定义一个用于创建对象的接口,这个接口由子类决定实例化哪一个类。该模式使一个类的实例化延迟到了子类。而子类可以重写接口方法以便创建的时候指定自己的对象类型。

工厂模式有以下特点:

  • 访问者只需要知道产品名,就可以从工厂获得对应实例;
  • 访问者不关心实例创建过程;
// 产品
class Product {
    constructor(name) {
        this.name = name
    }
    init() {
        console.log('init')
    }
    fun() {
        console.log('fun')
    }
}
// 工厂
class Factory {
    create(name) {
        return new Product(name)
    }
    // createOther...
}
// use
let factory = new Factory()
// 使用工厂创建实例
let p = factory.create('p1')
p.init()
p.fun()

示例

工厂模式比如: document.createElement 方法创建过 DOM 元素。虽然这个方法实际上很复杂,但其使用的就是工厂方法的思想:访问者只需提供标签名(如 divimg),那么这个方法就会返回对应的 DOM 元素。

再比如使用工厂模式实现吃饭的示例:

/* 饭店方法 */
class Restaurant {
    constructor() {
        this.menuData = {}
    }
    /* 创建菜品 */
    getMenu(menu) {
        if (!this.menuData[menu])
            throw new Error('没有这个菜')
        const { type, message } = this.menuData[menu]
        return new Menu(type, message)
    }
    /* 增加菜品种类 */
    addMenu(menu, type, message) {
        if (this.menuData[menu]) {
            console.Info('已有这个菜了')
            return
        }
        this.menuData[menu] = { type, message }
    }
    
    /* 移除菜品 */
    removeMenu(menu) {
        if (!this.menuData[menu]) return
        delete this.menuData[menu]
    }
}
/* 菜品类 */
class Menu {
    constructor(type, message) {
        this.type = type
        this.message = message
    }
    
    eat() { console.log(this.type + this.message) }
}
const restaurant = new Restaurant()
restaurant.addMenu('YuXiangRouSi', '鱼香肉丝', '真香~')  // 注册菜品
restaurant.addMenu('GongBaoJiDin', '宫保鸡丁', 'YYDS~')
const dish1 = restaurant.getMenu('YuXiangRouSi')
dish1.eat()
const dish2 = restaurant.getMenu('HongSaoPaiGu')

总结

场景

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

优点

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

缺点

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

什么时候不用

当被应用到错误的问题类型上时,这一模式会给应用程序引入大量不必要的复杂性.除非为创建对象提供一个接口是我们编写的库或者框架的一个设计上目标,否则我会建议使用明确的构造器,以避免不必要的开销。

由于对象的创建过程被高效的抽象在一个接口后面的事实,这也会给依赖于这个过程可能会有多复杂的单元测试带来问题。