JavaScript 設計模式

做者:gauseenjavascript

單例模式(Singleton Pattern

描述:html

只對外暴露一個對象前端

// 只建立一個實例

const mySingleton = (function () {
  let instance = null
  // 初始化方法
  let init = () => {
    console.log('init logic')

    return {
      someThing () {
        alert('hello world')
      },
    }
  }

  return {
    getInstance () {
      // 不存在 instance 建立新的,已存在則直接返回(只建立一次)
      if (!instance) {
        instance = init()
      }
      return instance
    },
  }
})()
// 每次都建立一個新的實例

const myBadSingleton = (function () {
  let instance = null
  // 初始化方法
  let init = () => {
    console.log('init logic')

    return {
      someThing () {
        alert('hello world')
      },
    }
  }

  return {
    getInstance () {
      // 每次都建立一個新的實例(建立屢次)
      instance = init()
      return instance
    },
  }
})()

觀察者模式(Observer Pattern

真實場景:java

以公衆號爲例,有些人訂閱(關注)了某公衆號,當公衆號發佈新的消息時,訂閱者就會收到該消息的推送git

描述:github

定義對象之間的依賴關係,每當對象更改狀態時,都會通知全部依賴項瀏覽器

// 觀察者
class Observer {
  constructor (name) {
    this.name = name
  }
  // 每一個觀察者都有一個 smile 事件
  update () {
    console.log(`${this.name} is updated`)
  }
}

// 主體
class Subject {
  constructor () {
    // 觀察者列表
    this.observers = []
  }
  // 添加觀察者到主體的觀察者列表
  subscribe (observer) {
    this.observers.push(observer)
  }
  // 通知全部觀察者
  notify () {
    this.observers.forEach(observerItem => {
      observerItem.update()
    })
  }
}

// 建立主體實例
const subject = new Subject()

// 有 2 個觀察者 observer0一、observer02
const observer01 = new Observer('observer_01')
const observer02 = new Observer('observer_02')

// 觀察者訂閱主體
subject.subscribe(observer01)
subject.subscribe(observer02)

// 通知全部觀察者
subject.notify()

發佈-訂閱模式(Publish/Subscribe Pattern

真實場景:post

報社將報紙發送給郵局,郵局按照訂閱關係將報紙發送給對應的人this

描述:spa

消息的發送者(稱爲發佈者)不會將消息直接發送給特定的接收者(稱爲訂閱者)。而是將發佈的消息分爲不一樣的類別,無需瞭解哪些訂閱者。一樣的,訂閱者能夠訂閱一個或多個類別,只接收感興趣的消息,無需瞭解哪些發佈者

// 訂閱者
class Subscriber {
  constructor (name) {
    this.name = name
  }
  // 每一個觀察者都有一個 smile 事件
  update () {
    console.log(`${this.name} is updated`)
  }
}

// 調度中心
class EventHub {
  constructor () {
    // 訂閱主題
    this.topics = {}
  }

  // 訂閱
  subscribe (topicType, subscriber) {
    if (!this.topics[topicType]) {
      this.topics[topicType] = []
    }

    this.topics[topicType].push(subscriber)
  }

  // 給對應主題下的訂閱者發佈內容
  publish (topicType) {
    let topicList = this.topics[topicType]

    if (!topicList) return

    topicList.forEach(subscriberItem => {
      subscriberItem.update()
    })
  }
}

// 建立一個「調度中心」
let eventHub = new EventHub()

// 建立 3 個訂閱者 subscriber0一、subscriber0二、subscriber03
let subscriber01 = new Subscriber('subscriber_01')
let subscriber02 = new Subscriber('subscriber_02')
let subscriber03 = new Subscriber('subscriber_03')

// 訂閱者註冊「調度中心」
eventHub.subscribe('typeA', subscriber01)
eventHub.subscribe('typeA', subscriber02)
eventHub.subscribe('typeB', subscriber03)

// 發佈者對 typeA 主題發佈內容
eventHub.publish('typeA')
// subscriber_01 is updated
// subscriber_02 is updated

// 發佈者對 typeB 主題發佈內容
eventHub.publish('typeA')
// subscriber_03 is updated

觀察者模式 vs 發佈-訂閱模式

如上圖

  • 觀察者模式中,主體(Subject)和觀察者(Observer)是強耦合,有直接的聯繫,相互知道對方的存在。
  • 發佈-訂閱模式中,主體(Subject)和觀察者(Observer)是鬆耦合,沒有直接的聯繫,是經過「事件通道」(調度中心)創建聯繫,互相不知道對方存在。

奇葩解釋:觀察者模式,沒中間商賺差價;發佈-訂閱模式,有中間商賺差價

工廠模式(Factory Pattern

class CarA {
  constructor (model, color) {
    console.log('CarA')
  }
}
class CarB {
  constructor (model, color) {
    console.log('CarB')
  }
}

class CarFactory {
  carA () { return CarA }
  carB () { return CarB }

  create (type) {
    let CarClass = this[type]()
    return new CarClass()
  }
}

let carFactory = new CarFactory()

// 經過 carFactory 建立不一樣的 car
let carA = carFactory.create('carA')
let carB = carFactory.create('carB')

適配器模式(Adapter Pattern

真實場景:

如今好多智能手機都去掉了 3.5mm 的耳機孔,取而代之的是 Type-C 接口耳機。可是你還想在新手機中使用 3.5mm 的老式耳機怎麼辦?

沒錯,那就須要用適配器,Type-C3.5mm 的適配器。以下圖

描述:

能夠將其餘不兼容的對象包裝在適配器中,使它與另外一個類兼容

// Type-C 耳機
class TypecHeadset {
  typeC () {
    console.log('Type-C 耳機已插入')
  }
}

// 3.5 毫米耳機
class Dot35mmHeadset {
  dot35mm () {
    console.log('3.5mm 耳機已插入')
  }
}

// 適配器(3.5 毫米 --> Type-C)

class Dot35mmToTypecAdapter {
  constructor (dot35mmHeadset) {
    this.dot35mmHeadset = dot35mmHeadset
  }

  typeC () {
    this.dot35mmHeadset.dot35mm()
  }
}

// 手機類(該手機只支持 Type-C 耳機)
class Phone {
  // 插入耳機孔
  insertTypeC (headset) {
    headset.typeC()
  }
}


let phone = new Phone()
// Type-C 耳機
let typecHeadset = new TypecHeadset()

// 3.5 毫米耳機
let dot35mmHeadset = new Dot35mmHeadset()

// 耳機適配器(3.5 毫米 --> Type-C)
let dot35mmToTypecAdapter = new Dot35mmToTypecAdapter(dot35mmHeadset)

phone.insertTypeC(typecHeadset) // Type-C 耳機已插入
phone.insertTypeC(dot35mmToTypecAdapter) // 3.5mm 耳機已插入

裝飾者模式(Decorator Pattern

真實場景:

先建一個基礎版本的手機,通過再加工對手機進行裝飾潤色,

描述:

裝飾者模式,容許你經過將對象包裝在裝飾器類的對象中,來動態更改對象在運行時的表現行爲

// 基礎 car 類
class SimpleCar {
  getCost () {
    return 10
  }

  getDescription () {
    return 'Simple car'
  }
}

// 奧迪
class AudiCar {
  constructor (car) {
    this.car = car
  }

  getCost () {
    return this.car.getCost() + 6
  }

  getDescription () {
    return this.car.getDescription() + ', Audi'
  }
}

// 寶馬
class BMWCar {
  constructor (car) {
    this.car = car
  }

  getCost () {
    return this.car.getCost() + 8
  }

  getDescription () {
    return this.car.getDescription() + ', BMW'
  }
}

// 基礎車信息
let simpleCar = new SimpleCar()
simpleCar.getCost() // 10
simpleCar.getDescription() // Simple car

// 奧迪車信息
let audiCar = new AudiCar(simpleCar)
audiCar.getCost() // 16
audiCar.getDescription() // Simple car, Audi

// 寶馬車信息
let bmwCar = new BMWCar(simpleCar)
bmwCar.getCost() // 18
bmwCar.getDescription() // Simple car, BMW

外觀模式(Facade Pattern

真實場景:

如何讓手機開機?很簡單,「長按電源鍵」便可開機。實際上手機內部處理了不少邏輯才能實現它。這就是所謂的外觀模式,將複雜轉爲簡潔。

例如,開發時經常使用於瀏覽器兼容性處理的代碼。

描述:

爲複雜的子系統提供了簡化統一的界面

// DOM 綁定事件的兼容性處理
function addEventFacade (el, eve, fn) {
  if (el.addEventListener) {
    el.addEventListener(eve, fn, false)
  } else if (el.attachEvent) {
    el.attachEvent('on' + eve, fn)
  } else {
    el['on' + eve] = fn
  }
}

代理模式(Proxy Pattern

真實場景:

你能夠直接去專賣店買手機(某海外品牌),也能夠經過代購幫你買手機(這樣價格會便宜些)。讓代購買手機這種方式,就是所謂的代理模式

js 原生也支持代理模式 Proxy

描述:

用一個對象來表示另外一個對象的功能

class Phone {
  constructor () {
    this.phoneName = 'Mate 90' // 手機名
    this.screen = '6.21' // 尺寸
    this.color = 'black' // 顏色
    this.chip = 'kirin' // 芯片
  }
}

class ProxyPerson {
  constructor(target) {
    this.target = target
  }
  buyPhone (phoneModel) {
    this.target.buyPhone(phoneModel)
  }
}

class Person {
  buyPhone (phoneModel) {
    console.log('哈哈,買到了: ', phoneModel.phoneName)
  }
}

let proxyPerson = new ProxyPerson(new Person())
proxyPerson.buyPhone(new Phone()) // 哈哈,買到了: Meta 90

策略模式(Strategy Pattern

真實場景:

商店打折促銷,某商品買 1 件原價,28 折,37

描述:

在代碼運行時,根據不一樣狀況切換不一樣的策略(方法)

// 策略組
const strategies = {
  num1 (price) {
    return price * 1
  },
  num2 (price) {
    return price * 0.8
  },
  num3 (price) {
    return price * 0.7
  },
}

// 獲取折後總價
function getDiscountPrice (n, price) {
  let _key = `num${n}`
  let discountedForItem = strategies[_key](price)
  return discountedForItem * n
}

getDiscountPrice(1, 10) // 10
getDiscountPrice(2, 10) // 16
getDiscountPrice(3, 10) // 21

狀態模式(State Pattern

描述:

在狀態更改時更改類的行爲(方法)

class Discount {
  constructor () {
    // 當前狀態
    this.currentState = ''
    // 全部狀態
    this.states = {
      typeA () {
        console.log('typeA 1')
      },
      typeB () {
        console.log('typeB 0.8')
      },
      typeC () {
        console.log('typeC 0.7')
      },
    }
  }
  // 更新當前狀態
  setState (_currentState) {
    this.currentState = _currentState
    return this
  }
  // 開始計算
  compute () {
    let currentStateHandler = this.states[this.currentState]
    currentStateHandler && currentStateHandler()
    return this
  }
}

let discount = new Discount()

discount
  .setState('typeA')
  .compute() // typeA 1
  .setState('typeC')
  .compute() // typeC 0.7
  .setState('typeB')
  .compute() // typeB 0.8

歡迎關注無廣告文章公衆號:學前端

參考

相關文章
相關標籤/搜索