设计模式(Javascript版)

五大设计原则 (SOLID)

S 单一职责原则

一个程序只做好一件事
功能独立拆开,保持每个部分干净独立 (便于组合与复用)

O 开放封闭原则

对扩展开放,对修改封闭
增加需求时,扩展新代码,而非修改已有代码

L 李氏置灰原则

子类能够覆盖父类
父类能够出现的地方,子类一样能出现
JS中使用较少(弱类型 & 继承使用较少)

I 接口独立原则

保持接口的单一独立,避免出现“胖接口”
JS中没有接口(typescript)除外,使用较少
类似于单一职责原则,这里更关注接口

D 依赖倒置原则

面向接口编程,依赖于抽象而不依赖于具体
使用方只关注于接口而不关注具体实现
JS中使用较少(没有接口 & 弱类型)

23种设计模式

设计模式分为:创建型(工厂模式、单例模式、原型模式)、结构型(适配器模式|装饰器模式|代理模式|外观模式|桥接模式|组合模式|享元模式)、行为型(策略模式|模板方法模式|观察者模式|迭代器模式|职责链模式|命令模式|备忘录模式|状态模式|访问者模式|中介者模式|解释器模式)。

工厂模式

将new 操作进行封装
遇到new时,就该考虑是否使用工厂模式。如:React.createElement(),源码自行查看哈
demo代码:

class Product {
  constructor (name) {
    name = name
  }
  init () {
    console.log('init====')
  }
  fun1() {
    console.log('fun1====')
  }
  fun2() {
    console.log('fun2====')
  }

}
class Factory {
  constructor () {}
  create() {
    return new Product()
  }
}
const factory = new Factory()
const product = factory.create()
console.log('product====', product)
product.init()
product.fun1()
product.fun2()

设计原则验证:
构造函数和创建者分离
符合开放封闭原则

单例模式

系统中被唯一使用的
一个类只有一个实例
demo代码:

class SingleObject {
  constructor () {}
  showCart () {
    console.log('显示购物车')
  }
}
SingleObject.getInstence = (function() {
  let instance
  return function() {
    if (!instance) {
      instance = new SingleObject()
    }
    return instance
  }

})()
const singleObject1 = SingleObject.getInstence()
const singleObject2 = SingleObject.getInstence()
singleObject1.showCart() // log 显示购物车
singleObject2.showCart()  // log 显示购物车
console.log('singleObject1===singleObject2', singleObject1===singleObject2)  // true

设计原则验证:
符合单一职责原则,只实例化一个对象
没法具体开放封闭原则,但是不违反开放封闭原则

适配器模式

旧接口对使用者不兼容
中间加个适配器转换接口

demo代码

// 原代码jquery的ajax请求,想要去除jquery库,这样就导致$.ajax 不能被使用者使用,需要适配兼容。
// 新ajax请求
ajax(someoptions)
// 原项目中的jquery的ajax请求,当去除jquery后,需要保证原来的ajax请求功能可用
$.ajax()

// 兼容适配:
const $ = {
  ajax: function(options) {
    return ajax(options)  // 兼容支持
  }
}

设计原则验证:
将旧接口与使用者分离
符合开放封闭原则

装饰器模式

为对象添加新功能
不改变其原有结构与功能

class Circle {
  constructor () {

  }
  draw () {
    console.log('这是一个圆形')
  }

}
class Decorator {
  constructor (circle) {
    this.circle = circle
  }
  draw () {
    console.log('--------------------')
    this.circle.draw()
    this.setRedBorder()
  }
  setRedBorder () {
    console.log('我是添加的红色边框')
  }

}
const circle = new Circle()
circle.draw()
const deCircle = new Decorator(circle)
deCircle.draw()
image.png

设计原则验证:
将现有对象与装饰器进行分离,两者独立存在
符合开放封闭原则

代理模式

使用者无法直接使用目标对象
通过中间加代理来授权和控制目标对象
例如:事件代理、
demo代码

class RealImg {
  constructor (fileName) {
    this.fileName = fileName
    this.loadImgFromDisk ()
  }
  display () {
    console.log('显示真实图片')
  }
  loadImgFromDisk () {
    console.log('加载本地图片'+this.fileName)
  }

}
class ProxyImg {
  constructor (fileName) {
    this.realImg = new RealImg(fileName)
  }
  display () {
    console.log('我是通过代理来显示图片' + this.realImg.fileName)
  }
}
const proxyImg = new ProxyImg('名称1')
proxyImg.display()

image.png

设计原则验证:
代理类和目标类分离,隔离开目标类和使用者
符合开放封闭原则

外观模式

外观模式是一个胖接口
为子系统一组接口提供一个高层接口
使用者使用这个高层接口

demo代码

function bindEvent(ele, type, selector, fn) {
  if (fn == null) {
    fn = selector
    selector = null
  }
  // ....
}

// 即可以传四个参数、也可以传三个参数
bindEvent(eleDom, 'click', '#selector', fn)
bindEvent(eleDom, 'click', fn)

设计原则验证:
不符合单一职责原则、开放封闭原则
谨慎使用,不滥用

观察者模式

发布 & 订阅
一对N
demo代码:

class Subject {
  constructor () {
    this.state = null
    this.observes = []
  }
  setState (state) {
    this.state = state
    this.notifyAllObservers()
  }
  getState () {
    return this.state
  }
  attach (observer) {
    this.observes.push(observer)
  }
  notifyAllObservers () {
    this.observes.forEach(observer => {
      observer.update()
    })
  }

}
class Observer {
  constructor (name, subject) {
    this.name = name
    this.subject = subject
    this.subject.attach(this) //初始化观察者把自己添加到主题
  }

  /**
   * 数据更新操作
   */
  update () {
    console.log(`${this.name}update, state: ${this.subject.getState()}`)
  }
}

// 实例
const s = new Subject()
const ob1 = new Observer('m1', s)
const ob2 = new Observer('m2', s)
const ob3 = new Observer('m3', s)
s.setState(1)
s.setState(2)
s.setState(3)
实例代码

设计原则验证:
主题和观察者分离,不是主动触发而是被动监听,两者解耦
符合开放封闭原则

迭代器模式

顺序访问一个集合
使用者无需知道集合的内部结构,能遍历访问每个元素
例如: jQuery each, ES6 Iterator
demo代码:

// 迭代器
class Iterator {
  constructor (container) {
    this.list = container.list
    this.index = 0
  }
  next () {
    if (this.hasNext()) {
      return this.list[this.index ++]
    }
    return null
  }
  hasNext () {
    if (this.index >= this.list.length) {
      return false
    }
    return true
  }
}

// 使用者
class Container {
  constructor (list) {
    this.list = list
  }

  // 生成迭代器工厂
  createIterator () {
    return new Iterator(this) // 传递当前Container 类的实例
  }
}

// demo 测试
const list1 = [1,2,3,4,5,6]
const container = new Container(list1)
const iterator = container.createIterator() // 获得遍历器

while (iterator.hasNext()) {
  console.log(iterator.next())
}
image.png

简单封装

function each(data) {
  // 生成遍历器
  let iterator = data[Symbol.iterator]()
  console.log(iterator.next())
  console.log(iterator.next())
  console.log(iterator.next())
  console.log(iterator.next())
  console.log(iterator.next())
  console.log(iterator.next())
  console.log(iterator.next())
  console.log(iterator.next())
  console.log(iterator.next())
  console.log(iterator.next())

}
let arr = [1,2,3,4,5,6,7,8]
each(arr)

打印结果


image.png

设计原则验证:
迭代器对象和目标对象分离
迭代器将使用者和目标对象隔离开
符合开放封闭原则

状态模式

一个对象有状态变化
每次状态变化都会触发一个逻辑
不能都是if... else 来控制

demo代码

class State {
  constructor (color) {
    this.color = color
  }
  handle (context) {
    console.log(`turn to ${this.color} light`)
    context.setState(this)
    // ... 其它操作逻辑
  }
}

// 主体
class Context {
  constructor () {
    this.state = null
  }
  getState () {
    return this.state
  }
  setState (state) {
    this.state = state
  }
}

const context = new Context()

const state1 = new State('red')
const state2 = new State('blue')
const state3 = new State('blank')

state1.handle(context) // 切换为red
state2.handle(context) // 切换为blue
state3.handle(context) // 切换为blank

结果:


image.png

设计原则验证:
讲状态对象与主题对象分离,状态的变化逻辑单独处理
符合开放封闭原则

原型模式

因重新创建一个对象new 个对象,开销比较大活不合适
基于原型对象(自己本身)clone 出一个对象
例如:Object.create(obj)

demo 代码:

const prototype = {
  getInfo: function() {
    return `name: ${this.name}, age: ${this.age}`
  },
  sayInfo: function() {
    console.log(this.getInfo())
  }
}

const obj1 = Object.create(prototype)
obj1.name = 'meng'
obj1.age = 32

obj1.sayInfo() // name: meng, age: 32
输出log
桥接模式

抽象与实现分离
符合开放封闭原则

demo代码:

class Color {
  constructor (name) {
    this.name = name
  }
}
class Shape {
  constructor (name, color) {
    this.name = name
    this.color = color
  }
  draw () {
    console.log(`颜色:${this.color.name}, 形状:${this.name}`)
  }
}
const red = new Color('red')
const circle = new Shape('circle', red)

const yellow = new Color('yellow')
const triangle = new Shape('triangle', yellow)

circle.draw()
triangle.draw()

log输出

设计原则验证:
抽象和实现分离,解耦
符合开放封闭原则

组合模式

生成树形结构,表示“整体-部分”的关系
让整体和部分都具有一致的操作性
例如:vnode 属性结构
demo代码

const vnode = {
  tag: 'div',
  attr: {
    id: "app",
    className: "container"
  },
  children: [
    {
      tag: 'div',
      attr: {
        id: 'item1',
        className: 'item'
      },
      children: ['text node']
    },
    {
      tag: 'div',
      attr: {
        id: 'item1',
        className: 'item'
      },
      children: ['text node']
    },
    {
      tag: 'div',
      attr: {
        id: 'item1',
        className: 'item'
      },
      children: ['text node']
    }
  ]
}

设计原则验证:
将整体和单个节点操作抽象出来
符合开放封闭原则

享元模式

共享内存,共享数据,共享部分与其它业务隔离,当需要修改或添加共享部分只修改共享块不用每个具体逻辑去修改
符合开放封闭原则

策略模式

不同策略,分开处理
避免出现大量if... else 或 switch...case
demo 代码:

class User {
  constructor (type) {
    this.type = type
  }
  // 需要改造buy
  buy () {
    if (this.type === 'ordinary') {
      console.log('ordinary buy')
    } else if (this.type === 'member') {
      console.log('member buy')
    } else if (this.type === 'vip') {
      console.log('vip buy')
    }
  }
}
const ordinaryUser = new User('ordinary')
const memberUser = new User('member')
const vipUser = new User('vip')
ordinaryUser.buy()
memberUser.buy()
vipUser.buy()
console.log('改造后=====================================')
// 改造后
class OrdinaryUser {
  buy () {
    console.log('ordinary buy')
  }
}
class MemberUser {
  buy () {
    console.log('member buy')
  }
}
class VipUser {
  buy () {
    console.log('vip buy')
  }
}
const ordinary = new OrdinaryUser()
const member = new MemberUser()
const vip = new VipUser()
ordinary.buy()
member.buy()
vip.buy()

log输出
模板方法模式

有几步处理或逻辑执行处理,进行统一封装在一个地方触发
demo代码

class Action {
  init () {
    this.handle1()
    this.handle2()
    this.handle3()
  }
  handle1 () {
    console.log('handle1')
  }
  handle2 () {
    console.log('handle2')
  }
  handle3 () {
    console.log('handle3')
  }
}
const action = new Action()
action.init()
职责链模式

各职责,链式操作
例如: promise.then.then、jquery 链式调用、node 的pipe管道流
demo代码:

class Action {
  constructor (name) {
    this.name = name
    this.nextAction = null
  }
  setNextAction (action) {
    this.nextAction = action
  }
  handle () {
    console.log(`${this.name} 操作`)
    if (this.nextAction != null) {
      this.nextAction.handle()
    }
  }
}

const action = new Action('组长')
const jlAction = new Action('经理')
const zjAction = new Action('总监')
action.setNextAction(jlAction)
jlAction.setNextAction(zjAction)
action.handle()

log 输出
命令模式

发布者与执行者隔离
通过中间加入命令者对象,作为中转站

demo代码:

// 接受者
class Receiver {
  // 执行
  exec() {
    console.log('执行')
  }
}

// 命令者
class Command {
  constructor (receiver) {
    this.recerver = receiver
  }
  cmd () {
    console.log('执行命令/号令')
    this.recerver.exec()
  }
}

// 发布者/触发者
class Invoker {
  constructor (command) {
    this.command = command
  }
  invoke () {
    console.log('发布/触发')
    this.command.cmd()
  }
}
const receiver = new Receiver()
const command = new Command(receiver)
const invoker = new Invoker(command)

invoker.invoke()
image.png

设计原则验证:
执行对象与命令对象分开
符合开放封闭原则

备忘录模式

随时记录一个状态的变化
随时可以恢复之前的某个状态(如撤销功能)

demo代码:

// 状态备忘
class Momento {
  constructor (content) {
    this.content = content
  }
  getContent () {
    return this.content
  }
}

// 备忘列表
class CareTaker{
  constructor () {
    this.list = []
  }

  // 添加备忘对象
  add (momento) {
    this.list.push(momento)
  }

  // 获取备忘对象
  get (index) {
    return this.list[index]
  }
}

// 编辑器/操作
class Editor {
  constructor () {
    this.content = null
  }
  setContent (content) {
    this.content = content
  }
  getContent () {
    return this.content
  }
  saveContentToMomento () {
    return new Momento(this.content)
  }
  getContentFromMomento (momento) {
    this.content = momento.getContent()
  }
}

const editor = new Editor()
const careTaker = new CareTaker()

editor.setContent('aaa')

editor.setContent('bbb')
careTaker.add(editor.saveContentToMomento()) // 将当前内容备忘列表

editor.setContent('ccc')
careTaker.add(editor.saveContentToMomento()) // 将当前内容备忘列表

editor.getContentFromMomento(careTaker.get(0)) // 从备忘列表获取第0个
console.log(editor.getContent())  // 获得当前内容
editor.getContentFromMomento(careTaker.get(1)) // 从备忘录列表获取第1个
console.log(editor.getContent()) // 获得当前内容
log输出

设计原则验证:
状态对象与使用者分开,解耦
符合开放封闭原则

中介者模式

通过关联对象,通过中介者隔离
符合开放封闭原则

demo代码:

class A {
  constructor () {
    this.number = 10
  }
  setNumber (num, meditor) {
    this.number = num
    if (meditor) {
      meditor.setBNumber()
    }
  }
}
class B {
  constructor () {
    this.number = 20
  }
  setNumber (num, meditor) {
    this.number = num
    if (meditor) {
      meditor.setANumber()
    }
  }
}
class Meditor {
  constructor (a, b) {
    this.a = a
    this.b = b
  }
  setANumber () {
    let number = this.a.number
    this.b.setNumber(number * 10)
  }
  setBNumber () {
    let number = this.b.number
    this.a.setNumber(number/10)
  }
}

const a = new A()
const b = new B()

const meditor = new Meditor(a, b)
a.setNumber(10, meditor)
b.setNumber(20, meditor)
console.log(a.number, b.number)
log 输出
访问者模式

将数据操作与数据结构进行分离
使用场景不多

解释器模式

描述语言语法如何定义,如何解释和编译
用于专业场景,如果babel工具解释编译类的

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 212,542评论 6 493
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 90,596评论 3 385
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 158,021评论 0 348
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 56,682评论 1 284
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 65,792评论 6 386
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 49,985评论 1 291
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 39,107评论 3 410
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 37,845评论 0 268
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 44,299评论 1 303
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 36,612评论 2 327
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 38,747评论 1 341
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 34,441评论 4 333
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 40,072评论 3 317
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 30,828评论 0 21
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,069评论 1 267
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 46,545评论 2 362
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 43,658评论 2 350

推荐阅读更多精彩内容