設(shè)計(jì)模式(Javascript版)

五大設(shè)計(jì)原則 (SOLID)

S 單一職責(zé)原則

一個(gè)程序只做好一件事
功能獨(dú)立拆開常侣,保持每個(gè)部分干凈獨(dú)立 (便于組合與復(fù)用)

O 開放封閉原則

對(duì)擴(kuò)展開放,對(duì)修改封閉
增加需求時(shí)弹渔,擴(kuò)展新代碼胳施,而非修改已有代碼

L 李氏置灰原則

子類能夠覆蓋父類
父類能夠出現(xiàn)的地方,子類一樣能出現(xiàn)
JS中使用較少(弱類型 & 繼承使用較少)

I 接口獨(dú)立原則

保持接口的單一獨(dú)立肢专,避免出現(xiàn)“胖接口”
JS中沒(méi)有接口(typescript)除外舞肆,使用較少
類似于單一職責(zé)原則,這里更關(guān)注接口

D 依賴倒置原則

面向接口編程博杖,依賴于抽象而不依賴于具體
使用方只關(guān)注于接口而不關(guān)注具體實(shí)現(xiàn)
JS中使用較少(沒(méi)有接口 & 弱類型)

23種設(shè)計(jì)模式

設(shè)計(jì)模式分為:創(chuàng)建型(工廠模式椿胯、單例模式、原型模式)剃根、結(jié)構(gòu)型(適配器模式|裝飾器模式|代理模式|外觀模式|橋接模式|組合模式|享元模式)哩盲、行為型(策略模式|模板方法模式|觀察者模式|迭代器模式|職責(zé)鏈模式|命令模式|備忘錄模式|狀態(tài)模式|訪問(wèn)者模式|中介者模式|解釋器模式)。

工廠模式

將new 操作進(jìn)行封裝
遇到new時(shí)狈醉,就該考慮是否使用工廠模式廉油。如: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()

設(shè)計(jì)原則驗(yàn)證:
構(gòu)造函數(shù)和創(chuàng)建者分離
符合開放封閉原則

單例模式

系統(tǒng)中被唯一使用的
一個(gè)類只有一個(gè)實(shí)例
demo代碼:

class SingleObject {
  constructor () {}
  showCart () {
    console.log('顯示購(gòu)物車')
  }
}
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 顯示購(gòu)物車
singleObject2.showCart()  // log 顯示購(gòu)物車
console.log('singleObject1===singleObject2', singleObject1===singleObject2)  // true

設(shè)計(jì)原則驗(yàn)證:
符合單一職責(zé)原則舔糖,只實(shí)例化一個(gè)對(duì)象
沒(méi)法具體開放封閉原則娱两,但是不違反開放封閉原則

適配器模式

舊接口對(duì)使用者不兼容
中間加個(gè)適配器轉(zhuǎn)換接口

demo代碼

// 原代碼jquery的ajax請(qǐng)求,想要去除jquery庫(kù)金吗,這樣就導(dǎo)致$.ajax 不能被使用者使用,需要適配兼容趣竣。
// 新ajax請(qǐng)求
ajax(someoptions)
// 原項(xiàng)目中的jquery的ajax請(qǐng)求摇庙,當(dāng)去除jquery后,需要保證原來(lái)的ajax請(qǐng)求功能可用
$.ajax()

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

設(shè)計(jì)原則驗(yàn)證:
將舊接口與使用者分離
符合開放封閉原則

裝飾器模式

為對(duì)象添加新功能
不改變其原有結(jié)構(gòu)與功能

class Circle {
  constructor () {

  }
  draw () {
    console.log('這是一個(gè)圓形')
  }

}
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

設(shè)計(jì)原則驗(yàn)證:
將現(xiàn)有對(duì)象與裝飾器進(jìn)行分離遥缕,兩者獨(dú)立存在
符合開放封閉原則

代理模式

使用者無(wú)法直接使用目標(biāo)對(duì)象
通過(guò)中間加代理來(lái)授權(quán)和控制目標(biāo)對(duì)象
例如:事件代理卫袒、
demo代碼

class RealImg {
  constructor (fileName) {
    this.fileName = fileName
    this.loadImgFromDisk ()
  }
  display () {
    console.log('顯示真實(shí)圖片')
  }
  loadImgFromDisk () {
    console.log('加載本地圖片'+this.fileName)
  }

}
class ProxyImg {
  constructor (fileName) {
    this.realImg = new RealImg(fileName)
  }
  display () {
    console.log('我是通過(guò)代理來(lái)顯示圖片' + this.realImg.fileName)
  }
}
const proxyImg = new ProxyImg('名稱1')
proxyImg.display()

image.png

設(shè)計(jì)原則驗(yàn)證:
代理類和目標(biāo)類分離,隔離開目標(biāo)類和使用者
符合開放封閉原則

外觀模式

外觀模式是一個(gè)胖接口
為子系統(tǒng)一組接口提供一個(gè)高層接口
使用者使用這個(gè)高層接口

demo代碼

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

// 即可以傳四個(gè)參數(shù)单匣、也可以傳三個(gè)參數(shù)
bindEvent(eleDom, 'click', '#selector', fn)
bindEvent(eleDom, 'click', fn)

設(shè)計(jì)原則驗(yàn)證:
不符合單一職責(zé)原則夕凝、開放封閉原則
謹(jǐn)慎使用宝穗,不濫用

觀察者模式

發(fā)布 & 訂閱
一對(duì)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) //初始化觀察者把自己添加到主題
  }

  /**
   * 數(shù)據(jù)更新操作
   */
  update () {
    console.log(`${this.name}update, state: ${this.subject.getState()}`)
  }
}

// 實(shí)例
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)
實(shí)例代碼

設(shè)計(jì)原則驗(yàn)證:
主題和觀察者分離,不是主動(dòng)觸發(fā)而是被動(dòng)監(jiān)聽码秉,兩者解耦
符合開放封閉原則

迭代器模式

順序訪問(wèn)一個(gè)集合
使用者無(wú)需知道集合的內(nèi)部結(jié)構(gòu)逮矛,能遍歷訪問(wèn)每個(gè)元素
例如: 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) // 傳遞當(dāng)前Container 類的實(shí)例
  }
}

// demo 測(cè)試
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

簡(jiǎn)單封裝

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)

打印結(jié)果


image.png

設(shè)計(jì)原則驗(yàn)證:
迭代器對(duì)象和目標(biāo)對(duì)象分離
迭代器將使用者和目標(biāo)對(duì)象隔離開
符合開放封閉原則

狀態(tài)模式

一個(gè)對(duì)象有狀態(tài)變化
每次狀態(tài)變化都會(huì)觸發(fā)一個(gè)邏輯
不能都是if... else 來(lái)控制

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

結(jié)果:


image.png

設(shè)計(jì)原則驗(yàn)證:
講狀態(tài)對(duì)象與主題對(duì)象分離,狀態(tài)的變化邏輯單獨(dú)處理
符合開放封閉原則

原型模式

因重新創(chuàng)建一個(gè)對(duì)象new 個(gè)對(duì)象转砖,開銷比較大活不合適
基于原型對(duì)象(自己本身)clone 出一個(gè)對(duì)象
例如: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
橋接模式

抽象與實(shí)現(xiàn)分離
符合開放封閉原則

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輸出

設(shè)計(jì)原則驗(yàn)證:
抽象和實(shí)現(xiàn)分離须鼎,解耦
符合開放封閉原則

組合模式

生成樹形結(jié)構(gòu),表示“整體-部分”的關(guān)系
讓整體和部分都具有一致的操作性
例如:vnode 屬性結(jié)構(gòu)
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']
    }
  ]
}

設(shè)計(jì)原則驗(yàn)證:
將整體和單個(gè)節(jié)點(diǎn)操作抽象出來(lái)
符合開放封閉原則

享元模式

共享內(nèi)存府蔗,共享數(shù)據(jù)晋控,共享部分與其它業(yè)務(wù)隔離,當(dāng)需要修改或添加共享部分只修改共享塊不用每個(gè)具體邏輯去修改
符合開放封閉原則

策略模式

不同策略姓赤,分開處理
避免出現(xiàn)大量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輸出
模板方法模式

有幾步處理或邏輯執(zhí)行處理赡译,進(jìn)行統(tǒng)一封裝在一個(gè)地方觸發(fā)
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()
職責(zé)鏈模式

各職責(zé),鏈?zhǔn)讲僮?br> 例如: promise.then.then不铆、jquery 鏈?zhǔn)秸{(diào)用蝌焚、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('組長(zhǎng)')
const jlAction = new Action('經(jīng)理')
const zjAction = new Action('總監(jiān)')
action.setNextAction(jlAction)
jlAction.setNextAction(zjAction)
action.handle()

log 輸出
命令模式

發(fā)布者與執(zhí)行者隔離
通過(guò)中間加入命令者對(duì)象,作為中轉(zhuǎn)站

demo代碼:

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

// 命令者
class Command {
  constructor (receiver) {
    this.recerver = receiver
  }
  cmd () {
    console.log('執(zhí)行命令/號(hào)令')
    this.recerver.exec()
  }
}

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

invoker.invoke()
image.png

設(shè)計(jì)原則驗(yàn)證:
執(zhí)行對(duì)象與命令對(duì)象分開
符合開放封閉原則

備忘錄模式

隨時(shí)記錄一個(gè)狀態(tài)的變化
隨時(shí)可以恢復(fù)之前的某個(gè)狀態(tài)(如撤銷功能)

demo代碼:

// 狀態(tài)備忘
class Momento {
  constructor (content) {
    this.content = content
  }
  getContent () {
    return this.content
  }
}

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

  // 添加備忘對(duì)象
  add (momento) {
    this.list.push(momento)
  }

  // 獲取備忘對(duì)象
  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()) // 將當(dāng)前內(nèi)容備忘列表

editor.setContent('ccc')
careTaker.add(editor.saveContentToMomento()) // 將當(dāng)前內(nèi)容備忘列表

editor.getContentFromMomento(careTaker.get(0)) // 從備忘列表獲取第0個(gè)
console.log(editor.getContent())  // 獲得當(dāng)前內(nèi)容
editor.getContentFromMomento(careTaker.get(1)) // 從備忘錄列表獲取第1個(gè)
console.log(editor.getContent()) // 獲得當(dāng)前內(nèi)容
log輸出

設(shè)計(jì)原則驗(yàn)證:
狀態(tài)對(duì)象與使用者分開狂男,解耦
符合開放封閉原則

中介者模式

通過(guò)關(guān)聯(lián)對(duì)象综看,通過(guò)中介者隔離
符合開放封閉原則

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 輸出
訪問(wèn)者模式

將數(shù)據(jù)操作與數(shù)據(jù)結(jié)構(gòu)進(jìn)行分離
使用場(chǎng)景不多

解釋器模式

描述語(yǔ)言語(yǔ)法如何定義,如何解釋和編譯
用于專業(yè)場(chǎng)景岖食,如果babel工具解釋編譯類的

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末红碑,一起剝皮案震驚了整個(gè)濱河市,隨后出現(xiàn)的幾起案子泡垃,更是在濱河造成了極大的恐慌析珊,老刑警劉巖,帶你破解...
    沈念sama閱讀 211,290評(píng)論 6 491
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件蔑穴,死亡現(xiàn)場(chǎng)離奇詭異忠寻,居然都是意外死亡,警方通過(guò)查閱死者的電腦和手機(jī)存和,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 90,107評(píng)論 2 385
  • 文/潘曉璐 我一進(jìn)店門奕剃,熙熙樓的掌柜王于貴愁眉苦臉地迎上來(lái),“玉大人捐腿,你說(shuō)我怎么就攤上這事纵朋。” “怎么了茄袖?”我有些...
    開封第一講書人閱讀 156,872評(píng)論 0 347
  • 文/不壞的土叔 我叫張陵操软,是天一觀的道長(zhǎng)。 經(jīng)常有香客問(wèn)我宪祥,道長(zhǎng)聂薪,這世上最難降的妖魔是什么家乘? 我笑而不...
    開封第一講書人閱讀 56,415評(píng)論 1 283
  • 正文 為了忘掉前任,我火速辦了婚禮藏澳,結(jié)果婚禮上仁锯,老公的妹妹穿的比我還像新娘。我一直安慰自己笆载,他們只是感情好扑馁,可當(dāng)我...
    茶點(diǎn)故事閱讀 65,453評(píng)論 6 385
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著凉驻,像睡著了一般腻要。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上涝登,一...
    開封第一講書人閱讀 49,784評(píng)論 1 290
  • 那天雄家,我揣著相機(jī)與錄音,去河邊找鬼胀滚。 笑死趟济,一個(gè)胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的咽笼。 我是一名探鬼主播顷编,決...
    沈念sama閱讀 38,927評(píng)論 3 406
  • 文/蒼蘭香墨 我猛地睜開眼,長(zhǎng)吁一口氣:“原來(lái)是場(chǎng)噩夢(mèng)啊……” “哼剑刑!你這毒婦竟也來(lái)了媳纬?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 37,691評(píng)論 0 266
  • 序言:老撾萬(wàn)榮一對(duì)情侶失蹤施掏,失蹤者是張志新(化名)和其女友劉穎钮惠,沒(méi)想到半個(gè)月后,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體七芭,經(jīng)...
    沈念sama閱讀 44,137評(píng)論 1 303
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡素挽,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 36,472評(píng)論 2 326
  • 正文 我和宋清朗相戀三年,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了狸驳。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片预明。...
    茶點(diǎn)故事閱讀 38,622評(píng)論 1 340
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡,死狀恐怖耙箍,靈堂內(nèi)的尸體忽然破棺而出贮庞,到底是詐尸還是另有隱情,我是刑警寧澤究西,帶...
    沈念sama閱讀 34,289評(píng)論 4 329
  • 正文 年R本政府宣布,位于F島的核電站物喷,受9級(jí)特大地震影響卤材,放射性物質(zhì)發(fā)生泄漏遮斥。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 39,887評(píng)論 3 312
  • 文/蒙蒙 一扇丛、第九天 我趴在偏房一處隱蔽的房頂上張望术吗。 院中可真熱鬧,春花似錦帆精、人聲如沸较屿。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,741評(píng)論 0 21
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽(yáng)隘蝎。三九已至,卻和暖如春襟企,著一層夾襖步出監(jiān)牢的瞬間嘱么,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 31,977評(píng)論 1 265
  • 我被黑心中介騙來(lái)泰國(guó)打工顽悼, 沒(méi)想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留曼振,地道東北人。 一個(gè)月前我還...
    沈念sama閱讀 46,316評(píng)論 2 360
  • 正文 我出身青樓蔚龙,卻偏偏與公主長(zhǎng)得像冰评,于是被迫代替她去往敵國(guó)和親。 傳聞我的和親對(duì)象是個(gè)殘疾皇子木羹,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 43,490評(píng)論 2 348

推薦閱讀更多精彩內(nèi)容