Oasis's Cloud

一个人的首要责任,就是要有雄心。雄心是一种高尚的激情,它可以采取多种合理的形式。
—— 《一个数学家的辩白》

JavaScript 设计模式学习笔记

作者:oasis


设计模式简介

Good code is like a love letter to the next developer who will maintain it! 好的代码就像一封写给下一个维护者的情书!

模式是一种可重复使用的解决方案,可以将模式应用于软件设计中反复出现的问题和主题。

设计模式优势:

如何构建模式

  1. 首先要确定模式的目标
  2. 概述模式的设计、实现、和目标
  3. 建立一下三个方面的关系
    • context(上下文)
    • 在上下文中建立的系统
    • 系统允许的配置

模式具备的要素: - 模式名称:描述模式用途 - 模式描述:简要说明该模式有助于实现什么目标 - 情境概述:模式有效满足用户需求的情境 - 问题陈述:对问题的陈述,以便我们理解模式的意图 - 解决方案:以可理解的步骤和感知列表描述如何解决用户的问题。 - 设计:描述模式的设计,尤其是用户与模式交互时的行为。 - 实施: 开发人员如何实施该模式的指南。 - 图示: 模式中类的可视化表示(如图表)。 - 示例: 该模式的最小实现形式。 - 核心前提: 为支持所描述模式的使用,还需要哪些其他模式? - 关系: 这种模式与哪些模式相似?它与其他模式有相似之处吗? - 已知用法: 该模式是否被广泛使用?如果是,在哪里使用,如何使用? - 讨论: 团队或作者对该模式令人兴奋的优点的看法。

设计模式分类

创建型

构造器模式(Constructor)

class Car {
  constructor(model, year, miles) {
    this.model = model;
    this.year = year;
    this.miles = miles;
  }
  toString() {
    return `${this.model} has done ${this.miles} miles`
  }
}
let civic = new Car('Honda Civic', 2017, 20000)
这里的构造函数模式存在 toString 方法重复声明的问题。每个实例都有一份。可以通过构造函数加原型的方式解决这个问题。

class Car {
  constructor(model, year, miles) {
    this.model = model;
    this.year = year;
    this.miles = miles;
  }

}
Car.prototype.toString = function() {
  return `${this.model} has done ${this.miles} miles`
}

模块化

通过 weakMap 可以实现模块化。

模块化可以帮助我们实现变量或方法的私有化。通过私有化,从而更好的对软件进行拆分,提高开发效率、降低理解成本、维护成本。

单例模式(Singleton)

场景: 将一个类的实例化限制为一个对象。通过此实例化对象,可以协调系统的操作,模块之间的协调。

缺点: - 不容易识别一个对象实例是不是单例 - 由于隐形依赖导致测试困难 - 因单例主要用于协调全局,所以执顺序相当重要

例子

let instance

function privateMethod() {}
class Singleton {
  constructor() {
    if(!instance) {
      instance = this
    }
    return instance
  }
  publicMethod() {}
}

React 中的状态管理

使用 React 进行网页开发的开发人员可以通过 Redux 或 React Context 等状态管理工具来依赖全局状态,而不是 Singletons。与 Singletons 不同的是,这些工具提供的是只读状态,而不是可变状态。

虽然全局状态的缺点并不会因为使用这些工具而神奇地消失,但由于组件无法直接更新全局状态,我们至少可以确保全局状态按照我们的意图发生变化。

原型模式(Prototype)

原型模式指类似与原型继承的对象创建模式

可通过 Object.create 实现

const Car = {
  name: 'F',
  drive() {},
  panic() {}
}
const mcar = Object.create(Car)
class VehiclePrototype { constructor(model) {
this.model = model; }
      getModel() {
          console.log('The model of this vehicle is..' +
this.model); }
Clone() {} }

class Vehicle extends VehiclePrototype { constructor(model) {
  super(model); }
  Clone() {
    return new Vehicle(this.model); }
}

const car = new Vehicle('Ford Escort'); const car2 = car.Clone(); car2.getModel();
const beget = (() => { class F {
          constructor() {}
      }
return proto => { F.prototype = proto; return new F();
}; })();

工厂模式(Factory)

工厂模式为创建对象提供一个通用接口,通过此接口我们可以创建指定的对象。

class Car {
  constructor({doors, state, color}) {
    this.doors = doors
    this.state = state
    this.color = color
  }
}
class Truck {
  constructor({state, wheelSize, color}) {
    this.state = state
    this.wheelSize = wheelSize
    this.color = color
  }
}

class Factory {
  constructor() {
    this.vehicleClass = Car
  }
  create(options) {
    switch (options.type) {
      case 'car':
        this.vehicleClass = Car
        break
      case 'truck':
        this.vehicleClass = Truck
        break
    }
    return new this.vehicleClass(options)
  }
}
何时使用工厂模式 - 当我们的对象或组件设置非常复杂时 - 当我们需要一种方便的方法来根据所处的环境生成不同的对象实例时 - 当我们处理许多共享相同属性的小对象或组件时 - 将对象与只需满足 API 契约(又称 duck typing)即可工作的其他对象的实例组合在一起时。这对解耦非常有用。

抽象工厂模式(Abstract Factories)

此模式可以理解为创建工厂的模式,通过此模式,可以创建服装工厂、汽车工厂、玻璃工厂、化工厂等。

class AbstractFactory {
  constructor() {
    this.types = {}
  }
  static getVehicle(type, customizations) {
    const V = this.types[type]
    return V ? new V(customizations) :null
  }
  static register(type, V) {
    this.types[type] = Vehicle
    return AbstractFactory
  }
}
AbstractFactory.register('car', Car)
AbstractFactory.register('truck', Truck)

结构型

门面模式(Facade)

在 jquery 中经常看到,例如选择器的函数,$(‘div’).css()

const addEvent = (el, ev, fn) => {
  if(el.addEventListener) {
    // el.addEventListener
  } else if(el.attachEvent) {
    // el.attachEvent
  } else {
    // el[`on${en}`]
  }
}

混入模式(Mixin)

通过 Mixin 可以扩展对象的功能。Mixin 有助于减少功能重复,提高复用率。

可以将 Mixin 视作对象子类化的一种替代方案。

React 推荐使用高阶组件和钩子来代替。

装饰器(Decorator Pattern)

可以将 Mixin 视作对象子类化的一种替代方案。

装饰器的目标是像现有类动态添加行为。

享元模式(Flyweight)

用于优化重复、缓慢和低效的共享数据。其目的是通过尽可能多地与相关对象共享数据,最大限度减少应用程序使用的内存。

在实践中,Flyweight 数据共享可能涉及多个对象使用的多个类似对象或数据结构,并将这些数据放入一个外部对象中。我们可以将该对象传递给依赖于这些数据的对象,而不是在每个对象中存储相同的数据。

可以将此模式应用于数据层或 DOM 层。

行为模型

观察者模式、发布订阅

中介者模式

命令模式

const CarManager = {
  requestInfo(model, id) {
    return `The information for ${model} with ID
  ${id} is foobar`;
  },
  buy(model, id) {
    return `You have successfully purchased Item
  ${id}, a ${model}`;
  },
  viewing(model, id) {
    return `You have booked a viewing of ${model} (
${id} ) `;
  }
}

CarManager.execute = function (name) {
  return (
    CarManager[name] && CarManager[name].apply(CarManager, [].slice().call(arguments, 1))
  )
}

CarManager.execute('buy', 'F', '001')

MV*

MVC

// MVC
// model 可以被 controll 更新,也可以被 view 更新
const model = {
  count: 0,
  increment: function() {
    this.count++
  }
}
const view = {
  countEl: document.getElementById('count'),
  incrementBtn: document.getElementById('increment'),
  render: function () {
    this.countEl.textContent = model.count
  }
  // 这里也可以调用 model 的方法
}
const controller = {
  init: function () {
    view.incrementBtn.addEventListener('click', this.increment)
  },
  increment: function() {
    // 更改 model
    model.increment()
    // 更新 view
    view.render()
  }
}
// MVP
const model = {
  count: 0,
  increment: function() {
    this.count++
  }
  // model 可以通知 Presenter
}
const view = {
  countEl: document.getElementById('count'),
  incrementBtn: document.getElementById('increment'),
  render: function () {
    this.countEl.textContent = model.count
  }
}
const Presenter = {
  init: function () {
    view.incrementBtn.addEventListener('click', this.increment)
  },
  increment: function() {
    // 更新 model
    model.increment()
    // 更新 view
    view.render()
  }
}
// MVVM
const model = {
  count: 0,
  increment: function() {
    this.count++
  }
}
const view = {
  countEl: document.getElementById('count'),
  incrementBtn: document.getElementById('increment'),
  render: function () {
    this.countEl.textContent = model.count
  }
}
const Presenter = {
  init: function () {
    view.incrementBtn.addEventListener('click', this.increment)
  },
  increment: function() {
    model.increment()
    view.render()
  }
}

模块化技术