前端设计模式-工厂模式

1,357 阅读8分钟

何为设计

  • 按照一种思路或者标准实现功能
  • 功能相同,可以有不同的设计方案实现
  • 随着需求增加,设计的作用才能体现

设计准则

  • 小即使美
  • 让每个程序只做好一件事
  • 快速建立原型
  • 舍弃高效率而取可移植行
  • 采用纯文本存储数据
  • 充分利用软件的杠杆效应(软件复用)
  • 避免强制性的用户界面
  • 让每个程序都成为过滤器
  • 沉默是金/各部分之和大于整体/寻求90%的解决方案

设计原则

单一职责原则
  • 一个程序只做好一件事
  • 如果功能过于复杂就拆分,每个部分保持独立
class Dog {
  run () {
    console.log('Dog run')
  }
}

class Duck {
  run () {
    console.log('Duck run')
  }
}

let dog = new Dog()
let duck = new Duck()

dog.run()
duck.run()
开放封闭原则
  • 对扩展开放,对修改封闭
  • 增加需求时,扩展新代码,而非修改己有代码

非开放封闭原则代码

// 不符合开闭原则
class Duck {
  run () { console.log('Duck run') }
}

class Dog {
  run () { console.log('Dog run') }
}

const animalRun = (animal: any) => {
  if (animal instanceof Duck) {
    // TODO
  }
  if (animal instanceof Dog) {
    // TODO
  }
  // ...
}

animalRun(Duck)
animalRun(Dog)

当我们的类越来越多时候层层嵌套就会导致过于复杂

如果我们换一种方式呢?

class Animal {
  run () {}
}

class Dog extends Animal{
  run() {
    // TODO
  }
}

class Duck extends Animal{
  run() {
    // TODO
  }
}

class Pig extends Animal{
  run() {
    // TODO
  }
}

这样我们就是先的对扩展开放(子类),对修改关闭(父类)

李式置换原则
  • 引用基类(父类) 的地方必须能够透明的使用子类的对象
  • 子类可以扩展父类的功能,但不能修改父类原有功能
class Parent {
  public num1: number
  public num2: number
  constructor(num1: number, num2: number) {
    this.num1 = num1
    this.num2 = num2
  }
  add () {
    console.log(this.num1 + this.num2)
  }
  multiple () {
    console.log('我是一个抽象方法')
  }
}

class Child extends Parent {
  constructor(num1: number, num2: number) {
    super(num1, num2)
  }
  sub () {
    console.log(this.num1 - this.num2)
  }
  // 重写父类抽象方法multiple
  multiple() {
    console.log(this.num1 * this.num2)
  }
}

let child = new Child(1, 2)
child.multiple()
接口独立原则
  • 接口保持单一对立,避免出现‘胖接口’
  • 类似单一职责(业务逻辑)这里更加关注接口(方法数量)
// 动物基类接口
interface Animal {
  eat(): void
  sleep(): void
}

// 爬行类动物接口
interface Reptile {
  crawl(): void // 爬
}

// 鱼类接口
interface Fish {
  swimming(): void
}

// 爬行类动物类
class ReptileAnimal implements Animal, Reptile{
  eat(): void {
  }
  sleep(): void {
  }
  crawl(): void {
  }
}

// 鱼类
class FishAnimal implements Animal, Fish{
  eat(): void {
  }
  sleep(): void {
  }
  swimming(): void {
  }
}
依赖倒置原则
  • 面向接口编程,依赖与抽象而不是具体
  • 高层模块不应该依赖底层模块,二者都应该依赖于抽象,抽象不应该依赖细节,细节应该依赖抽象

// 吃的抽象类
abstract class Eat {
  // 抽象方法
  abstract goEat (): string
}

// 实现类(细节)
class Apple extends Eat{
  goEat(): string {
    return '吃苹果'
  }
}

class Banana extends Eat{
  goEat(): string {
    return '吃香蕉'
  }
}

// 顶层类
class People {
  public name: string
  constructor(name: string) {
    this.name = name
  }
  gotoEat <T extends Eat> (food: T) {
    console.log(this.name + food.goEat())
  }
}

// 实例化食物
let apple = new Apple()
let banana = new Banana()

// 顶层类实例
let peopleA = new People('jen')
let peopleB = new People('吃花椒酱的喵')

peopleA.gotoEat(apple)
peopleB.gotoEat(banana)

设计模式

  • 设计模式是一套被反复使用的,多人知晓的,经过分类编目的代码设计经验总结
  • 使用设计模式是为了重用代码,让代码更容易被他人理解,保证代码可靠性
  • 在软件设计中,对某些的理解也许形成了条件反射,当适当的场景出现时他们可以很多的找到某种模式作为解决方案
工厂模式
  • 工厂模式是用来创建对象的一种最常用的设计模式(创建性)我们不暴露创建对象的具体逻辑,而是将逻辑封装在一个函数中,那么这个函数就可以被看作一个工厂
  • 工厂模式根据抽象程度又分为三种:简单工厂,工厂方法,抽象方法
工厂模式-简单工厂
  • 简单工厂模式又称为静态工厂方法模式,可以根据不同的参数返回不同类型的实例
  • 简单工厂模式专门定义一个类来负责创建其他类的实例,被创建的类通常具有共同的父类

  1. 工厂类角色:这是本模式的核心,含有一定的商业逻辑和判断逻辑,用来创建产品
  2. 抽象产品角色:他一般是具体产品继承的父类或者实现的接口
  3. 具体产品角色:工厂类所创建的对象就是次角色的实例
// 抽象产品 - 运算类
interface UFunc {
  getResult (numA: number, numB: number): number
}

// 具体产品 - 乘法
class Multiplication implements UFunc {
  getResult(numA, numB): number {
    return numA * numB
  }
}

// 具体产品 - 加法
class Addition implements UFunc {
  getResult(numA, numB): number {
    return numA + numB
  }
}

// 具体产品 - 减法
class Subtraction implements UFunc {
  getResult(numA, numB): number {
    return numA - numB
  }
}

// 具体产品 - 除法
class Division implements UFunc {
  getResult(numA, numB): number {
    return numA / numB
  }
}

// 简单工厂 createOperate
interface CreateOperate {
  createOperate (operation: string): UFunc
}

class CreateOperateFactory implements  CreateOperate {
  createOperate (operation) {
    switch (operation) {
      case '+':
        return new Addition()
      case '-':
        return new Subtraction()
      case '*':
        return new Multiplication()
      case '/':
        return new Division()
      default:
        return
    }
  }
}

new CreateOperateFactory().createOperate('+').getResult(1, 2)
工厂模式 - 工厂方法

工厂方法模式去掉了简单工厂模式中方法的静态属性,使得它可以被子类继承。这样在简单工厂模式中里集中在工厂方法中上的压力可以有工厂方法中的不同子类来分担

  1. 抽象工厂角色:这是工厂方法中方法模式的核心。是具体工厂角色必须实现的接口或者继承的父类
  2. 具体工厂角色:含有和具体业务逻辑有关的代码。创建对应的具体产品的对象
  3. 抽象产品角色:它是具体产品继承的父类或者实现的接口
  4. 具体产品角色:具体工厂角色创建的对象就是此角色的实例
// 抽象产品类 - 运算类
interface UFunc {
  getResult (numA: number, numB: number): number
}

// 具体产品 - 乘法
class Multiplication implements UFunc {
  getResult(numA, numB): number {
    return numA * numB
  }
}

// 具体产品 - 加法
class Addition implements UFunc {
  getResult(numA, numB): number {
    return numA + numB
  }
}

// 具体产品 - 减法
class Subtraction implements UFunc {
  getResult(numA, numB): number {
    return numA - numB
  }
}

// 具体产品 - 除法
class Division implements UFunc {
  getResult(numA, numB): number {
    return numA / numB
  }
}

// 抽象工厂类接口 - 工厂类
interface CreateOperate {
  createOperate (): UFunc
}

// 具体工厂 - 加法工厂
class AdditionFactory implements CreateOperate {
  createOperate (): UFunc {
    return new Addition()
  }
}

// 具体工厂 - 乘法工厂
class MultiplicationFactory implements CreateOperate {
  createOperate(): UFunc {
    return new Multiplication()
  }
}

// 具体工厂 - 减法工厂
class SubtractionFactory implements CreateOperate {
  createOperate(): UFunc {
    return new Subtraction()
  }
}

// 具体工厂 - 除法工厂
class DivisionFactory implements CreateOperate {
  createOperate(): UFunc {
    return new Division()
  }
}

// 实列
new AdditionFactory().createOperate().getResult(1, 2)
new SubtractionFactory().createOperate().getResult(1, 2)
工厂模式 - 抽象工厂

抽象工厂模式是工厂方法模式的泛化版,工厂方法模式是一种特殊的工厂模式,在工厂方法模式中一个具体工厂只能生产一种产品而抽象工厂模式一个具体的工厂可以生成多个产品

  1. 抽象工厂角色:这是一个方法模式的核心,是具体工厂角色必须实现的接口或者必须实现的父类
  2. 具体工厂角色:含有和具体业务逻辑有关的代码。创建对应的具体产品的对象
  3. 抽象产品角色:它是具体产品继承父类或者实现的接口
  4. 具体产品角色:具体工厂角色所创建的对象就是此角色的实例
// AbsLocation 产品接口
interface AbsLocation {
  startLocationWithResult (): void
}

// AbsMapView 产品接口
interface AbsMapView {
  initWithFrame (): AbsMapView
  getView (): void
}

// 具体产品 - AbsBaiDuLocation
class AbsBaiDuLocation implements AbsLocation {
  startLocationWithResult () {
    console.log('AbsBaiDuLocation 产品')
  }
}

// 具体产品 - AbsGaoDeLocation
class AbsGaoDeLocation implements AbsLocation {
  startLocationWithResult () {
    console.log('AbsGaoDeLocation 产品')
  }
}

// 具体产品 - AbsBaiDuMapView
class AbsBaiDuMapView implements AbsMapView {
  initWithFrame(): AbsMapView {
    return new AbsBaiDuMapView()
  }

  getView(): void {
    console.log('AbsBaiDuMapView 产品')
  }
}

// 具体产品
class AbsGaoDuMapView implements AbsMapView {
  initWithFrame(): AbsMapView {
    return new AbsBaiDuMapView()
  }

  getView(): void {
    console.log('AbsGaoDuMapView 产品')
  }
}

// 抽象工厂类 - AbsMapFactory
interface AbsMapFactory {
  createLocation (): AbsLocation
  createMapView (): AbsMapView
}

// 创建具体工厂 - AbsBaiDuMapFactory
class AbsBaiDuMapFactory implements AbsMapFactory {
  createLocation(): AbsLocation {
    return new AbsBaiDuLocation()
  }

  createMapView(): AbsMapView {
    return new AbsBaiDuMapView()
  }
}

// 创建工厂 - AbsGaoDuMapFactory
class AbsGaoDuMapFactory implements AbsMapFactory {
  createMapView(): AbsMapView {
    return new AbsGaoDuMapView()
  }

  createLocation(): AbsLocation {
    return new AbsGaoDeLocation()
  }
}

// 创建实例
new AbsBaiDuMapFactory().createLocation().startLocationWithResult()
new AbsGaoDuMapFactory().createLocation().startLocationWithResult()

优点:

  1. 当一个产品族中的多个对象被设计一起工作时,它能够包装客户端始终只是用同一个产品族中的对象,这对一些需要根据当前环境来决定其行为的软件系统来说是一种非常实用的设计模式、
  2. 增加新的具体工厂和产品族很方便,无需修改已有系统,符合“开闭原则”

缺点:

  1. 在添加新的产品对象时,难以扩展抽象工厂来生产新种类的产品,这是因为在抽象工厂中规定了所有可能被创建的产品集合,要支持新的就要对接口进行扩展,而这涉及对抽象工厂角色已经其所有子类的修改,较大不便
  2. 开闭原则的倾斜性(增加新的工厂和产品族容易,增加新的产品等级结构麻烦

  1. 产品等级结构:产品等级结构既产品的继承结构,如一个抽象类是电视机,其子类有海尔电视机,海信电视机,TCL电视机,则抽象电视机与具体品牌的电视机之间构成一个产品等级结构,抽象电视机 是父类,而具体品牌的电视机是其子类
  2. 产品族:在抽象工厂模式中,产品族是指同一个工厂生成的,位于不同的等级结构的一组产品,如海尔生产的海尔电视机,海尔冰箱,电视机位于电视机等级结构中,冰箱位于冰箱等级结构中
工厂模式实战
<!DOCTYPE html>
<html lang="en">
<head>
  <meta charset="UTF-8">
  <title>工厂模式实现弹出框</title>
  <style>
    * {
      margin: 0;
      padding: 0:
    }
    .layer {
      width: 300px;
      padding: 20px;
      background: #fff;
      border: 1px solid #ccc;
      border-radius: 5px;
      box-shadow: 0 3px 5px #bbb;
      /* 在容器中水平垂直居中显示 */
      position: absolute;    /* 弹窗是浮出层,位置是绝对定位的,不会影响原文档流 */
      left: 50%;
      top: 50%;      /* 相对于容器的宽度 */
      transform: translate(-50%, -50%);  /* 相对于元素自身宽度 */
    }
    .layer h2 {
      font-size: 16px;
      border-bottom: 1px solid #ddd;
      padding-bottom: 5px;
      margin-bottom: 20px;
    }
    .layer p {
      text-indent: 2em;
      font-size: 14px;
      line-height: 1.5;
    }
    .layer button {
      display: block;
      width: 100px;
      height: 30px;
      line-height: 30px;
      border: 0;
      border-radius: 5px;
      color: #fff;
      background: #333;
      margin: 10px auto 0;
      text-align: center;
      cursor: pointer;
    }
  </style>
</head>
<body>
  <script>
    class PromptBox {
      constructor(text, template) {
        this.text = text
        this.template = template
        this.dom = null
        this.init()
      }
      // 初始化
      init () {
        this.initDom()
        this.initEvent()
      }
      // 初始化节点
      initDom () {
        let node = document.createElement('div')
        node.innerHTML = this.template.replace('{{ text }}', this.text)
        this.dom = node.childNodes[0] // div节点以及其中的内容保存下来,显示函数用于添加到页面中
      }
      // 注册事件
      initEvent () {
        this.dom.addEventListener('click', function (event) {
          if (event.target.tagName === 'BUTTON') {
            this.hide()
          }
        }.bind(this))
      }
      // 显示
      show () {
        document.body.appendChild(this.dom)
      }
      // 隐藏
      hide () {
        document.body.removeChild(this.dom)
      }
    }

    class Creator{
      creator(name, template){
        return new PromptBox(name, template)
      }
    }
    // 弹窗组件的HTML结构:模板字符串,定制需求
    let template = `<div class='layer'>
                          <h2>提示</h2>
                          <p>{{ text }}</p>
                          <button>知道了</button>
                      </div>`

    let factory = new Creator()
    let boxUI = factory.creator('提示!!', template)
    boxUI.show()
</script>
</body>
</html>
效果图