前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >JS 设计模式之抽象工厂模式(创建型)

JS 设计模式之抽象工厂模式(创建型)

作者头像
Leophen
发布2021-06-10 16:48:22
5050
发布2021-06-10 16:48:22
举报
文章被收录于专栏:Web前端开发

工厂模式·抽象工厂——理解“开放封闭”

上一节提到工厂模式中的简单工厂,这节开始理解抽象工厂。

1、从一个不简单的简单工厂开始

在实际的业务中,我们往往面对的复杂度并非数个类、一个工厂可以解决,而是需要动用多个工厂。

我们来看一个简单工厂函数:

代码语言:javascript
复制
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)
  }

乍一看没什么问题,但是经不起推敲呀。

首先,我们把 Boss 这个角色和普通员工塞进了一个工厂。

我们知道,Boss 和基层员工在职能上是有差别的,例如权限。

有一些系统,比如员工绩效评估的打分入口,就只有 Boss 点得进去,除此之外还有许多操作只有 Boss 可执行,因此我们需要对 Boss 的对象进行单独的逻辑处理。

怎么处理?去修改 Factory 的函数体、增加 Boss 相关的判断和处理逻辑吗?

单从功能实现上来说,没问题。

但这么做其实是在挖坑——因为公司不仅仅只有这两类人,还有外包同学、保安等,他们的权限、职能也和基层员工不一样。

如果延续这个思路,每考虑到一个新的员工群体,就回去修改一次 Factory 的函数体,这样做糟糕透了——没有遵守开放封闭原则。

开放封闭原则的内容:对拓展开放,对修改封闭

开放封闭原则说得更准确点,就是软件实体(类、模块、函数)可以扩展,但是不可修改。以上对 Boss 单独逻辑处理就错在我们不是在拓展,而是在修改。

2、抽象工厂模式

先来看这么一个示例:

我们知道手机的基本组成是操作系统(OS)和硬件(HardWare)。

如果我要开一个手机工厂,那需要准备好操作系统和硬件,才能实现手机的量产。

现在我还不知道下一个生产线要生产一台什么样的手机,只知道要有这两部分组成,所以可以用一个抽象类来约定住这台手机的组成:

代码语言:javascript
复制
class MobilePhoneFactory {
  // 提供操作系统的接口
  createOS() {
    throw new Error("抽象工厂方法不允许直接调用,你需要将我重写!");
  }
  // 提供硬件的接口
  createHardWare() {
    throw new Error("抽象工厂方法不允许直接调用,你需要将我重写!");
  }
}

上面这个类,除了约定手机流水线的通用能力之外,啥也不干。

如果你尝试让它干点啥,比如 new 一个 MobilePhoneFactory 实例,并尝试调用它的实例方法,它还报错,并提醒你“阿 Sir,我不是让你拿去 new 一个实例的,我就是个定规矩的”。

在抽象工厂模式里,上面这个类就是我们食物链顶端最大的 Boss——AbstractFactory(抽象工厂)。

抽象工厂不干活,具体工厂(ConcreteFactory)来干活!

当我们明确了生产方案,明确某一条手机生产流水线具体要生产什么样的手机了之后,就可以化抽象为具体。

比如我现在想要一个专门生产 Android 系统 + 高通硬件的手机的生产线,并给手机型号起名叫 UPhone,那我就可以为 UPhone 定制一个具体工厂:

代码语言:javascript
复制
// 具体工厂继承自抽象工厂
class UPhoneFactory extends MobilePhoneFactory {
  createOS() {
    // 提供安卓系统实例
    return new AndroidOS();
  }
  createHardWare() {
    // 提供高通硬件实例
    return new QualcommHardWare();
  }
}

这里我们在提供安卓系统的时候,调用了两个构造函数:AndroidOS 和 QualcommHardWare,它们分别用于生成具体的操作系统和硬件实例。

像这种被我们拿来用于 new 出具体对象的类,叫做具体产品类(ConcreteProduct)。

具体产品类往往不会孤立存在,不同的具体产品类往往有着共同的功能。

比如安卓系统类和苹果系统类,它们都是操作系统,都有着可以操控手机硬件系统这样一个最基本的功能。

因此我们可以用一个抽象产品(AbstractProduct)类来声明这一类产品应该具有的基本功能:

代码语言:javascript
复制
// 定义操作系统这类产品的抽象产品类
class OS {
  controlHardWare() {
    throw new Error('抽象产品方法不允许直接调用,你需要将我重写!');
  }
}

// 定义具体操作系统的具体产品类
class AndroidOS extends OS {
  controlHardWare() {
    console.log('我会用安卓的方式去操作硬件')
  }
}

class AppleOS extends OS {
  controlHardWare() {
    console.log('我会用苹果的方式去操作硬件')
  }
}
...

硬件类产品同理:

代码语言:javascript
复制
// 定义手机硬件这类产品的抽象产品类
class HardWare {
  // 手机硬件的共性方法,这里提取了“根据命令运转”这个共性
  operateByOrder() {
    throw new Error('抽象产品方法不允许直接调用,你需要将我重写!');
  }
}

// 定义具体硬件的具体产品类
class QualcommHardWare extends HardWare {
  operateByOrder() {
    console.log('我会用高通的方式去运转')
  }
}

class MiWare extends HardWare {
  operateByOrder() {
    console.log('我会用小米的方式去运转')
  }
}
...

这样一来,当我们需要生产一台 UPhone 手机时,我们只需要这样做:

代码语言:javascript
复制
// 这是我的手机
const myPhone = new UPhoneFactory();
// 让它拥有操作系统
const myOS = myPhone.createOS();
// 让它拥有硬件
const myHardWare = myPhone.createHardWare();
// 启动操作系统 (输出‘我会用安卓的方式去操作硬件’)
myOS.controlHardWare();
// 唤醒硬件 (输出‘我会用高通的方式去运转’)
myHardWare.operateByOrder();

假如有一天,Uphone 过气了,我们就不需要对抽象工厂 MobilePhoneFactory 做任何修改,只需要拓展它的种类:

代码语言:javascript
复制
class UPhoneFactory extends MobilePhoneFactory {
  createOS() {
    // 操作系统实现代码
  }
  createHardWare() {
    // 硬件实现代码
  }
}

这个操作对原有的系统不会造成任何潜在影响,所谓的“对拓展开放,对修改封闭”就这么圆满实现了。

3、总结

回头对比一下简单工厂和抽象工厂的思路,可以知道:

相同点在于二者都尝试去分离一个系统中变与不变的部分。

不同点在于场景的复杂度:

在简单工厂的使用场景里,处理的对象是类,并且是一些非常好对付的类——它们的共性容易抽离,同时因为逻辑本身比较简单,故而不苛求代码可扩展性。

抽象工厂本质上处理的其实也是类,但是是一帮非常棘手、繁杂的类,这些类中不仅能划分出门派,还能划分出等级,同时存在着千变万化的扩展可能性——这使得我们必须对共性作更特别的处理、使用抽象类去降低扩展的成本,同时需要对类的性质作划分,于是有了这样的四个关键角色:

① 抽象工厂(抽象类,不能被用于生成具体实例)

用于声明最终目标产品的共性。

在一个系统里,抽象工厂可以有多个,每个抽象工厂对应的这类产品,被称为“产品族”。

② 具体工厂(用于生成产品族里的一个具体的产品)

继承自抽象工厂、实现了抽象工厂里声明的那些方法,用于创建具体产品的类。

③ 抽象产品(抽象类,不能被用于生成具体实例)

上面我们看到,具体工厂里实现的接口,会依赖一些类,这些类对应到各种各样的具体的细粒度产品(比如操作系统、硬件等),这些具体产品类的共性各自抽离,便对应到了各自的抽象产品类。

④ 具体产品(用于生成产品族中的一个具体产品所依赖的更细粒度产品)

比如我们上文中具体的一种操作系统、或具体的一种硬件等。

抽象工厂模式的定义,是围绕一个超级工厂创建其他工厂。

抽象工厂目前来说在 JS 世界里也应用得并不广泛,所以大家不必拘泥于细节,只需留意以下三点:

  • 学会用 ES6 模拟 JAVA 中的抽象类;
  • 了解抽象工厂模式中四个角色的定位与作用;
  • 对“开放封闭原则”形成自己的理解,知道它好在哪,知道执行它的必要性。
本文参与 腾讯云自媒体同步曝光计划,分享自作者个人站点/博客。
原始发表:2021-03-10 ,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 作者个人站点/博客 前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体同步曝光计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • 工厂模式·抽象工厂——理解“开放封闭”
    • 1、从一个不简单的简单工厂开始
      • 2、抽象工厂模式
        • 3、总结
          • ① 抽象工厂(抽象类,不能被用于生成具体实例)
          • ② 具体工厂(用于生成产品族里的一个具体的产品)
          • ③ 抽象产品(抽象类,不能被用于生成具体实例)
          • ④ 具体产品(用于生成产品族中的一个具体产品所依赖的更细粒度产品)
      领券
      问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档