带你了解前端设计模式-🍀工厂模式🍀

1,443 阅读11分钟

设计模式是一套被反复使用、多数人知晓、经过分类编目的、代码设计经验的总结。它是为了可重用代码,让代码更容易的被他人理解并保证代码的可靠性。

设计模式合集链接

带你了解前端设计模式-🍀单例模式🍀

带你了解前端设计模式-🍀工厂模式🍀

带你了解前端设计模式-🍀策略模式🍀

带你了解前端设计模式-🍀代理模式🍀

带你了解前端设计模式-🍀观察者模式🍀

带你了解前端设计模式-🍀发布-订阅模式🍀

概述

工厂模式可以将对象的创建过程封装在工厂类中,使得消费者(使用者)生产者(实现者)解耦。通过工厂模式,消费者不需要直接使用 new 关键字来创建对象,而是通过调用工厂类的方法来获取所需的对象

工厂模式结构:

工厂模式.png

工厂模式的核心目标始终是生产物品

什么是工厂模式?

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

通过工厂模式,我们可以通过调用工厂类的方法来获取所需的对象,而无需直接使用 new 关键字来创建对象。

通俗理解就是:

在工厂模式中,工厂充当了一个生产标准规格商品的地方。它接收参数(原料),根据`参数的不同选择实例化合适的对象(特定规格的产品),并将其返回给使用者。

这样做的好处之一是解耦。使用工厂模式,使用者无需直接依赖具体的对象实现,而只需通过工厂来获取所需的对象。这样,当需要更换或扩展对象实现时,使用者的代码不需要做出太多改动,只需要修改工厂的实现即可。

另外,通过工厂模式可以实现面向抽象编程。工厂模式中的抽象产品定义了产品的接口或抽象类,使用者只需要操作抽象产品,而无需关心具体产品的细节。这样可以提高代码的灵活性和可维护性,也方便进行类型检查和静态分析。

在创建对象时,不暴露具体的逻辑,而是将逻辑封装在函数中,那么这个函数就可以被视为一个工厂。工厂模式根据抽象程度的不同可以分为:简单工厂工厂方法抽象工厂

一个简单的例子,我们去餐馆吃饭,只需要按照菜单上的菜名进行点餐,然后菜做出来之后,不需要知道这些菜是怎么做的,只管吃就好了。在这里面,餐馆就相当于工厂,负责生产菜品,访问者通过餐馆就可以拿到产品。

这个例子有一个特点,访问者只需要产品名就可以从工厂获得实例,访问者不需要关心实例创建的过程。工厂模式其实就是将创建对象的过程单独封装,它的目的,就是为了实现无脑传参。

实现方式

特点简单工厂工厂方法抽象工厂
定义一个工厂类负责所有产品的实例化定义一个接口用于创建对象,让子类决定实例化哪一个类提供一个创建一系列相关或相互依赖对象的接口
实现简单创建对象的逻辑集中在单个工厂类中创建对象的逻辑分布在不同的工厂类中创建对象的逻辑分散在不同的工厂接口中
扩展性随着产品种类的增加,工厂类会变得庞大且不易维护可以通过增加新的工厂类来扩展新产品的生产可以随意增加产品系列或者产品族
类型一对一关系一对多关系多对多关系
示例简单实例化对象通过工厂基类和具体子类实例化对象通过抽象工厂接口和具体工厂类实例化对象

生产实例(商品)的工厂可以是小作坊,只生产少量简单的商品,也可以是大型的复杂工厂,因此基于场景复杂程度又有多个工厂模式:

类别说明
简单工厂(Simple Factory)小作坊,适用于少量对象的创建,集中式管理,使用简单、扩展不便
工厂方法(Factory Method)正规小工厂,每种产品一个独立工厂,偏平化扩展
抽象工厂(Abstract Factory)集团化大厂,产品种类、层级众多,需要多层级的工厂来管理,代码也稍复杂

简单工厂(Simple Factory)

简单工厂模式又叫静态工厂模式,由一个工厂对象决定创建某一种产品对象类的实例。主要用来创建同一类对象

以下是一个使用简单工厂模式创建不同角色用户对象的示例代码。在这个示例中,我们定义了一个 User 类,并通过静态方法 getInstance 来创建不同角色的用户对象。

class User {
  constructor(opt) {
    this.name = opt.name;
    this.viewPage = opt.viewPage;
  }
​
  static getInstance(role) {
    switch (role) {
      case 'superAdmin':
        return new User({ name: '超级管理员', viewPage: ['首页', '应用数据', '权限管理'] });
      case 'admin':
        return new User({ name: '管理员', viewPage: ['首页', '应用数据'] });
      case 'user':
        return new User({ name: '普通用户', viewPage: ['首页'] });
      default:
        throw new Error('参数错误, 可选参数:superAdmin、admin、user')
    }
  }
}
​
let superAdmin = User.getInstance('superAdmin');
console.log(superAdmin); // 输出: User { name: "超级管理员", viewPage: ["首页", "应用数据", "权限管理"] }let admin = User.getInstance('admin');
console.log(admin); // 输出: User { name: "管理员", viewPage: ["首页", "应用数据"] }let normalUser = User.getInstance('user');
console.log(normalUser); // 输出: User { name: "普通用户", viewPage: ["首页"] }

在上面的示例中,我们通过调用 User.getInstance 方法并传入相应的角色参数('superAdmin'、'admin'、'user'),来获取对应角色的用户对象。

通过这种方式,我们可以根据不同的角色快速创建相应的用户对象,并设置其不同的属性值。这样,我们可以在后续的代码中使用这些用户对象,进行相应的操作。

简单工厂模式的优势就在于,只需要一个参数,就可以获得所需的对象,无需知道对象创建的具体细节。但是,在函数内部包含了对象所有的创建逻辑,和判断逻辑的代码,如果判断逻辑很多,或者代码逻辑很复杂,这样工厂函数就会变的很复杂,很庞大,难以维护。所以,简单工厂只适合以下情况:

  • 创建的对象数量较少
  • 创建的对象的逻辑不是很复杂

工厂方法(Factory Method)

工厂方法模式定义了一个用于创建对象的工厂方法,由子类决定具体创建哪个类的对象。工厂方法模式将对象的创建延迟到子类中,客户端只需要依赖抽象的工厂接口,而不需要关心具体的产品类。

与简单工厂模式相比,工厂方法模式更加灵活和可扩展。它支持创建多个不同类型的产品,并且每个产品都由对应的工厂类负责创建,符合单一职责原则。当需要添加新的产品时,只需要添加相应的具体产品类和对应的具体工厂类,而无需修改已有的代码。

在上面的简单工厂模式中。每次添加一个构造函数都要修改两处代码,现在对它加以改造:

class User {
  constructor(name = '', viewPage = []) {
    if(new.target === User) {
      throw new Error('抽象类不能实例化!');
    }
    this.name = name;
    this.viewPage = viewPage;
  }
}
class UserFactory extends User {
  constructor(name, viewPage) {
    super(name, viewPage)
  }
  create(role) {
    switch (role) {
      case 'superAdmin': 
        return new UserFactory( '超级管理员', ['首页', '应用数据', '权限管理'] );
        break;
      case 'admin':
        return new User({ name: '管理员', viewPage: ['首页', '应用数据'] });
        break;
      case 'user':
        return new UserFactory( '普通用户', ['首页'] );
        break;
      default:
        throw new Error('参数错误, 可选参数:superAdmin、admin、user')
    }
  }
}
let userFactory = new UserFactory();
let superAdmin = userFactory.create('superAdmin');
let admin = userFactory.create('admin');
let user = userFactory.create('user');

工厂方法可以看做是一个实例化对象的工厂,它需要做的就是实例化对象。

抽象工厂模式(Abstract Factory)

上面两种方式都是直接生成实例,而抽象工厂模式并不能直接生成实例,而是用于产品类簇的创建

抽象工厂模式通过提供一个接口来创建一系列相关的产品,这些产品通常属于不同的类,但又有着某种关联或者依赖关系。通过使用抽象工厂模式,我们可以确保创建的产品是相互配合使用的,并且隐藏了具体产品的实现细节。

在抽象工厂模式中,有两个核心概念:抽象工厂具体工厂。抽象工厂定义了一组用于创建产品的抽象方法,而具体工厂则实现了这些抽象方法,并负责创建具体的产品。

具体工厂类根据不同的需求实现了抽象工厂中定义的方法,每个方法都会返回一个具体的产品对象。这些产品对象通常属于同一个产品簇,它们具有相似的特性或者功能。

通过使用抽象工厂模式,我们可以在不修改客户端代码的情况下,更换具体工厂类,从而创建不同簇的产品。这种灵活性使得我们可以根据需要动态地改变所使用的产品。

在网站登录中,会使用不同的第三方登录方式,例如微信、QQ、微博,这三类账号就是对应的类簇。在抽象工厂中,类簇一般用于父类的定义,并在父类中定义一些抽象的方法(声明但不能使用的方法),在通过抽象工厂让子类继承父类,所以,抽象工厂实际上就是实现子类继承父类的方法。

// 抽象工厂接口
class AuthFactory {
  createLogin() {}
}
​
// 具体工厂类 - 微信登录
class WeChatAuthFactory extends AuthFactory {
  createLogin() {
    return new WeChatLogin();
  }
}
​
// 具体工厂类 - QQ登录
class QQAuthFactory extends AuthFactory {
  createLogin() {
    return new QQLogin();
  }
}
​
// 具体工厂类 - 微博登录
class WeiboAuthFactory extends AuthFactory {
  createLogin() {
    return new WeiboLogin();
  }
}
​
// 抽象登录类
class AbstractLogin {
  authenticate() {}
}
​
// 具体登录类 - 微信登录
class WeChatLogin extends AbstractLogin {
  authenticate() {
    console.log('WeChat login');
  }
}
​
// 具体登录类 - QQ登录
class QQLogin extends AbstractLogin {
  authenticate() {
    console.log('QQ login');
  }
}
​
// 具体登录类 - 微博登录
class WeiboLogin extends AbstractLogin {
  authenticate() {
    console.log('Weibo login');
  }
}
​
// 使用示例
function performAuthentication(factory) {
  const login = factory.createLogin();
  
  login.authenticate();
}
​
const weChatFactory = new WeChatAuthFactory();
performAuthentication(weChatFactory);
​
const qqFactory = new QQAuthFactory();
performAuthentication(qqFactory);
​
const weiboFactory = new WeiboAuthFactory();
performAuthentication(weiboFactory);

总结

总结:

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

优缺点

工厂模式是一种创建型设计模式,用于封装对象的创建过程,将其与具体使用代码分离。下面是工厂模式的一些优点和缺点:

优点

  • 封装性好:工厂模式可以将对象的创建和使用分离,客户端只需要知道使用工厂方法来创建对象即可,无需了解具体的创建细节。
  • 扩展性强:通过工厂模式,可以方便地添加新的产品类并创建对应的工厂类,而不影响已有的代码结构和客户端使用。
  • 符合开放封闭原则:新增产品类不需修改原有代码,只需新增对应的产品类和工厂类,符合软件设计原则。

缺点

  • 增加复杂度:引入工厂模式会增加代码的复杂度和结构,可能需要新增很多工厂类,增加了代码量。
  • 不适用于简单对象的创建:工厂模式适合创建复杂对象的场景,如果只需要创建简单对象,使用工厂模式可能过于繁琐。
  • 需要预先知道具体产品类:工厂模式需要事先了解可能会创建的产品类,而且随着产品种类的增加,工厂类也会增多。

适用场景

  • 对象的创建比较复杂,而访问者无需知道创建的具体流程
  • 处理大量具有相同属性的小对象

总之,工厂模式通过封装对象的创建过程,提供了一种灵活、可扩展的方式来创建对象,降低了客户端和具体产品类之间的耦合度。然而,它也会增加一定的复杂性和维护成本,因此在使用时需要根据具体情况权衡其优点和缺点。

参考文献

工厂模式

前端小白变形记:你要学会这些设计模式!首发:工厂模式

前端设计模式:工厂模式(Factory)