「淺嘗」JavaScript設計模式

什麼是設計模式?

設計模式:根據不一樣場景建立不一樣類型的對象的套路被稱爲設計模式。javascript

使用設計模式的主要緣由?

①可維護性:設計模式有助於下降模塊間的耦合程度,這使對代碼進行重構和換用不一樣的模塊變得更容易,也使得程序員在大型團隊中的工做以及與其餘程序員的合做變得更容易。
②溝通:設計模式爲處理不一樣類型的對象提供了一套通用的術語,程序員能夠更簡明地描述本身的系統的工做方式,你不用進行冗長的說明,每每一句話,我是用了什麼設計模式,每一個模式有本身的名稱,這意味着你能夠在較高層面上進行討論,而沒必要涉足過多的細節
③性能:某些模式是起優化做用的模式,能夠大幅度提升程序的運行速度,並減小須要傳送到客戶端的代碼量前端

設計模式

實現設計模式比較容,懂得應該在何時使用什麼模式比較困難,未搞懂設計模式的用途就盲目套用,是一種不安全的作法,你應該保證所選用的模式就是最恰當的那種,而且不要過分犧牲性能。vue

1、單例模式

<font size="4" color="red">確保單體對象只存在一個實例。</font>

業務場景:當咱們使用node啓動一個服務鏈接數據庫的時候咱們通常會建立一個鏈接數據庫的實例(這個實例就是單例)。每一個請求對於數據的請求都是經過這個單例的,不會爲沒個請求去建立單獨的實例,一個單例便於統一管理。java

var Single = (function () {
  var instance
  var createSingle = function (name) {
    if (instance) {
      return instance
    }
    this.name = name
    instance = this
    return instance
  }
  return createSingle
})();

var single1 = new Single('1')
var single2 = new Single('2')
console.log(single1.name) // '1'
console.log(single2.name) // '2'

2、工廠模式

<font size="4" color="red">工廠模式使用一個方法來決定究竟要實例化哪一個具體的類。</font>
  • 簡單工廠模式

使用一個總的工廠來完成對於全部類的分發。<font color="red">情景再現:</font>一個<font color="red">寵物店</font>裏面有着許多寵物,客人能夠經過向寵物店<font color="red">傳遞消息</font> 因而次日咱們就能夠到寵物店獲取一隻貓<font color="red">貓</font>
①工廠(寵物店) ②傳參(傳遞消息) ③實例化對應類(貓)node

class Cat{
   constructor() {
       this.name = '貓'
   }
}
class Dog{
   constructor() {
       this.name = '狗'
   }
}
class Factory {
    constructor(role){
      return this.switchRole(role)
    }
    switchRole(role){
      switch(role){
        case '貓':
            return new Cat()
        case '狗':
            return new Dog()
        default:
            return {}
      }
    }
}
var dog = new Factory('狗') // {name:'貓'}
var cat = new Factory('貓') // {name:'狗'}

簡單的工廠模式咱們已經實現了,這時候咱們須要又要發散咱們的小腦殼去好好揣摩這個模式,咱們發現若是每次寵物店又有了新的寵物能夠出售,例現在天寵物店引進了烏龜、寵物豬,那咱們不只要先實現相對應的類,還要在<font color="red">Factory</font>中的<font color="red">switchRole()</font>補充條件。若是建立實例的方法的邏輯發生變化,工廠的類就會被屢次修改程序員

  • 複雜工廠模式

既然簡單工廠模式,不能知足咱們所有的業務需求,那就只能進化變身了。<font color="red">《javascript設計模式》</font> 給了定義:真正的工廠模式與簡單工廠模式區別在於,它不是另外使用一個類或對象建立實例,而是使用<font color="red">子類</font>工廠是一個將其成員對象的實例推送到子類中進行的類 也就是咱們在定義咱們看到真正的工廠模式,是提供一個工廠的<font color="red">父類抽象類</font>,對於根據傳參實現實例化的過程是放在子類中實現。數據庫

再思考一個問題:貓、狗、烏龜、寵物豬、這些類是否能夠再進行細分出現了加菲貓、波斯貓、柴犬、阿拉斯加等子類。在購買寵物的時候是否須要特別的條件? 上述工廠的子類能夠解決這個問題:出現了<font color="red">專賣店</font>專門賣貓的,賣狗的,賣寵物豬的,這些專賣店(<font color="red">工廠子類</font>)各自維護本身的類,當你須要新的專賣店你能夠從新實現一個<font color="red">工廠子類</font>設計模式

class Cat{
   constructor() {
       this.name = '貓'
   }
}
class Garfield {
   constructor() {
       this.name = '加菲貓'
   }
}
class Persian {
   constructor() {
       this.name = '波斯貓'
   }
}
class Dog{
   constructor() {
       this.name = '狗'
   }
}
// 定義成爲抽象類,工廠的父類,不接受任何修改
class Factory {
    constructor(role){
      return this.createModule(role)
    }
    createModule(role){
        return new Error('我是抽象類不要改我,也不要是實例化我')
    }
}
// 貓的專賣工廠,須要重寫父類那裏繼承來的返回實例的方法。購買貓的邏輯能夠放在找個類中實現。
class CatFactory extends Factory{
    constructor(role){
      super(role)
    }
    // 重寫createFactory的方法
    createModule(role){
        switch(role){
            case '加菲貓':
                return new Garfield()
            case '波斯貓':
                return new Persian()
            default:
                return {}
          }
    }
}
.... 狗、寵物豬、烏龜的均可以從新繼承父類Factory。
var catFac = new CatFactory('波斯貓')
console.log(catFac)

總結:<font color="red">複雜工廠模式</font>將原有的簡單工廠模式下的工廠類變爲<font color="red">抽象類</font>根據輸出<font color="red">實例</font>的不一樣來構建不一樣的<font color="red">工廠子類</font>。這樣既不會修改到工廠抽象類,符合設計原則,又提供了可拓展性。api

3、橋接模式

<font size="4" color="red">將抽象與其實現隔離開來,以便兩者獨立變化。</font>

你們看到上面的那句話會以爲有點摸不清頭腦看一下下面的代碼:數組

//一個事件的監聽,點擊元素得到id,根據得到的id咱們發送請求查詢對應id的貓
element.addEventListener('click',getCatById)
var getCatById = function(e){
   var id = this.id
   asyncRequst('Get',`cat.url?id=${id}`,function(resp){
       console.log('我已經獲取了信息')
   })
}

你們看一下getCatById這個api函數咱們能夠理解爲<font color="red">抽象</font> 而點擊這個過程是<font color="red">實現</font>的效果,可是咱們發現getCatById與實現邏輯並無徹底分割,<font color="red">getCatById</font>是一個只能工做在瀏覽器中的API,根據事件監聽器函數的工做機制,事件對象天然會被做爲第一個參數傳遞給這個函數,在本例中並無使用,可是咱們看到了<font color="red">var id = this.id</font>,若是你要對這個API作單元測試,或者命令環境中執行,那就只能祝你好運了,任何一個API都不該該<font color="red">把它與任何特定環境攪在一塊兒</font>

// 改寫getCatById 將抽象與現實徹底隔離,抽象徹底依賴傳參,同時咱們在別的地方也能夠引用,不受制與業務
var getCatById = function(id,callback){
   asyncRequst('Get',`cat.url?id=${id}`,function(resp){
       console.log('我已經獲取了信息')
   })
}

這個時候咱們發現瞭如今抽象出來的getCatById已經不能直接做爲事件函數的回調了,這時候咱們要隆重的請出咱們<font color="red">橋接模式</font> 此處應該有撒花。

element.addEventListener('click',getCatByIdBridge)
var getCatByIdBridge(e){ // getCatByIdBridge 橋接元素
    getCatById(this.id,function(cat){
        console.log('request cat')
    })
}

咱們能夠看到<font color="red">getCatByIdBridge</font>這個就是橋接模式的產物。溝通抽象與現實的<font color="red">橋樑</font>。有了這層橋接,getCatById這個API的使用範圍就大大的拓寬了,沒有與事件對象捆綁在了一塊兒。

總結:在實現API的時候,橋接模式很是有用,咱們用這個模式來<font color="red">弱化</font>API與使用他的類和對象之間的耦合,這種模式對於js中常見的<font color="red">事件驅動</font>有大的裨益。

4、策略模式

<font size="4" color="red">定義一系列的規則,根據環境的不一樣咱們執行不一樣的規則,來避免大量重複的工做。</font>

策略模式根據上述的說法能夠隱隱的猜到了,要實行策略模式咱們須要兩個類:首先咱們須要一個定義了一系列規則的<font color="red">策略類</font>這個是整個策略模式的基石。以後有一個暴露對外方法的<font color="red">環境類</font><font color="red">經過傳遞不一樣的參數給環境類,環境類從策類中選取不一樣的方法執行,最終返回結果</font>

業務場景:做爲一個前端不免跑不了一個表單驗證。當你不使用庫的時候不免須要手寫一些正則校驗、判空、判類型等方式。
這個時候你的代碼就是以下的:

// vue下的表單校驗
checkForm () {
  if (this.form.realName === '') {
    Toast.fail('真實姓名不能爲空')
    return false
  }
  if (!/^[\u4e00-\u9fa5]{0,}$/.test(this.form.realName)) {
    Toast.fail('請輸入中文')
    return false
  }
  if (!/(^\d{15}$)|(^\d{18}$)|(^\d{17}(\d|X|x)$)/.test(this.form.idCardNum)) {
    Toast.fail('請輸入正確的身份證格式')
    return false
  }
  return true
},

注意:這樣寫是沒有問題的,可是咱們仔細看這個代碼會發現,這個代碼幾乎<font color="red">不可複用</font>,即便在其餘的地方咱們須要對字段斷定中文,咱們就須要從新再用正則斷定,其次當一個表單內部須要校驗字段不少,那麼上述方法將會過於<font color="red">冗餘</font>

修改思路:將校驗的具體的實現咱們放入策略類,這樣對於可重複使用檢驗規則咱們使用一個單個方法統一維護,以後將暴露環境類給各個業務場景使用,傳入所需的方法名、檢測值而後調用策略返回結果。

// 策略類爲表單校驗正則及及自定義的規則
var rules = {
  // 是否中文
  isChinese: function (value) {
    if (/^[\u4e00-\u9fa5]{0,}$/.test(value)) {
      return true
    } else {
      return false
    }
  },
  //  是否不爲空
  notNull: function (value) {
    if (value !== '') {
      return true
    } else {
      return false
    }
  },
.... // 不一樣策略
}


// 環境類
var validate = function(rule,value) {
    return rules[rule](value);
};

//業務執行
const isChinese = validate('isChinese',value)
const notNull = validate('notNull',value)
const checkResult = isChinese||notNull
if(checkResult){
    .....
}

總結:一個簡單的策略模式就已經實現了,將校驗的抽象方法放在策略類,而後根據參數的不一樣去調用不一樣的策略,實現了策略的複用,也實現了代碼的簡化。可是這樣的策略是你心中真正的愛麼兄弟?

缺點:上述的代碼已經知足了策略模式,可是對於具體業務的支持彷佛還有點小瑕疵,首先上述的業務執行,你會發現對於單個校驗咱們返回的是boolean值,若是咱們須要有失敗的回調函數的調用,那就仍是須要<font color="red">判斷語句</font>的加入,真的是魚和熊掌不可兼得,也就是說上述的代碼,<font color="red">只能支持表單項所有檢測完成後總的失敗回調,對於當個表單項的失敗沒法支持,用戶每每輸入完成所有表單項後才被告知表單中有錯誤,並且還不知道具體是哪一個</font>。

優化:修改環境類,參數對象造成的數組或單個參數對象傳入,參數對象傳入時提供失敗函數,對外提供一個檢驗方法,遍歷檢查參數對象數組,所有成功返回true,失敗執行失敗回調函數同時返回false。

class Validate {
  constructor () {
    this.cache = []
    if (Array.isArray(arguments[0])) {
      // 數組的單個元素{rule:string[規則的名稱],value:any[校驗的值],efn:fn[失敗的回調]}
      this.cache = arguments[0]
    }
    // 傳入參數爲對象時
    this.cache.push(arguments[0])
  }
  // 執行校驗,失敗的話執行失敗的回調,成功靜默,全部的參數符合規則則返回true
  valid () {
    let i = 0
    for (const value of this.cache) {
      if (rules[value.rule] && rules[value.rule](value.value)) {
        i++
      } else {
        if (value.efn) value.efn()
        return false
      }
    }
    return i === this.cache.length
  }
}

總結:策略模式能夠應用<font color="red">使用判斷語句多的時候,判斷內容爲同種模式</font>的狀況下。策略模式中的策略類能夠進行復用,從而避免不少地方的複製粘貼,獨立的策略類易於理解、切換、拓展。

5、中介者模式

<font size="4" color="red">中介者模式的做用就是解除對象與對象之間的緊耦合關係。對象與對象中的通訊以中介者爲媒介觸發。中介者使各對象之間耦合鬆散,並且能夠獨立地改變它們之間的交互。中介者模式使網狀的多對多關係變成了相對簡單的一對多關係。</font>

場景分析:設定兩個角色<font color="red">房東</font>、<font color="red">租客</font>。<font color="red">房東</font>與<font color="red">租客</font>組成的是多對多的網格關係,一個<font color="red">房東</font>能夠與多名<font color="red">租客</font>接觸,詢問是否有租房意願,同理<font color="red">租客</font>能夠向多名<font color="red">房東</font>打探是否有房源出售。

前置條件咱們實例化兩個角色<font color="red">房東</font>、<font color="red">租客</font>,咱們使用<font color="red">簡單工廠模式</font>。

// 房東類
class Owener{
    constructor(name){
        this.name=name
    }
    // 想要出租
    sell(renters=[]){
        renters.map(renter=>{
            renter.onMessage(`${this.name}想要出租房子`)
        })
    }
    // 收到消息
    onMessage(msg){
        console.log(`${this.name}知道了${msg}`)
    }
}
// 租客類
class Renter{
    constructor(name){
        this.name=name
    }
    // 收到消息
    onMessage(msg){
        console.log(`${this.name}知道了${msg}`)
    }
}
class Factory{
    constructor(role,name){  
        this.name = name
        return this.switchRole(role,name)
    }
    switchRole(role,name){
        switch (role) {
            case 'owner':
                return new Owener(name)
            case 'renter':
                return new Renter(name)
            default:
                return new Error('無當前角色')
        }
    }
}
var owner1 = new Factory('owner','房東一')
var owner2 = new Factory('owner','房東二')
var renter1 = new Factory('renter','租客一')
var renter2 = new Factory('renter','租客二')
var renter3 = new Factory('renter','租客三')
owner.sell([renter1,renter2,renter3])

上述代碼完成了<font color="red">房東一</font> 發佈出租房子的意願,三名<font color="red">租客</font>接受到了消息。反向同理也能夠實現。租客發佈消息,房東收到,可是咱們發現<font color="red">房東</font> 與<font color="red">租客</font>之間。仍是存在緊密的耦合,<font color="red">房東</font>與<font color="red">租客</font>之間不一樣的關係須要自行不一樣的方法,那整個房東類會變得及其臃腫,反之亦然。因而咱們引入<font color="red">中介者模式</font>

<font color="red">中介者模式</font>在上述例子中理解爲<font color="red">中介公司</font>,扮演了維護<font color="red">房東</font> 和<font color="red">租客</font>關係的橋樑,<font color="red">租客</font>和<font color="red">房東</font>類只要考慮各自的行爲,不須要考慮行爲會給那些關係對象帶來影響。這些任務交給咱們的
<font color="red">中介者</font>

優化:生成一個<font color="red">中介者</font>來處理<font color="red">房東</font>與<font color="red">租客</font>的相互調用,以及肯定相互關係,<font color="red">中介者</font>提供一個雙向的方法,供<font color="red">房東</font>和<font color="red">租客</font>調用,<font color="red">而後實現對相關對象的分發</font>。

class Owener{
    constructor(name){
        this.name=name
    }
    // 想要出租
    sell(mediator){
        mediator.sendMessage('owner',`${this.name}想要出租房子`)
    }
    // 收到消息
    onMessage(msg){
        console.log(`${this.name}知道了${msg}`)
    }
}
class Renter{
    constructor(name){
        this.name=name
    }
    // 想要租房
    rent(mediator){
        mediator.sendMessage('renter',`${this.name}想要租房子`)
    }
    // 收到消息
    onMessage(msg){
        console.log(`${this.name}知道了${msg}`)
    }
}
class Factory{
    constructor(role,name){  
        this.name = name
        return this.switchRole(role,name)
    }
    switchRole(role,name){
        switch (role) {
            case 'owner':
                return new Owener(name)
                break;
            case 'renter':
                return new Renter(name)
                break;
            default:
                return new Error('無當前角色')
                break;
        }
    }
}
class Mediator{
    constructor(owner,renter){
      // 房東集合
      this.owner = owner
      // 房客集合
      this.renter = renter
    }
    sendMessage(role,msg){
       if(role === 'owner'){
           for(const value of this.renter){
               value.onMessage(msg)
           }
       }
       if(role === 'renter'){
           for(const value of this.owner){
               value.onMessage(msg)
           }
       }
    }
}
var owner1 = new Factory('owner','房東一')
var owner2 = new Factory('owner','房東二')
var renter1 = new Factory('renter','租客一')
var renter2 = new Factory('renter','租客二')
var renter3 = new Factory('renter','租客三')
var mediator = new Mediator([owner1,owner2],[renter1,renter2,renter3])
owner1.sell(mediator)
租客一知道了房東一想要出租房子
租客二知道了房東一想要出租房子
租客三知道了房東一想要出租房子
renter1.rent(mediator)
房東一知道了租客一想要租房子
房東二知道了租客一想要租房子

總結:<font color="red">房東</font>、<font color="red">租客</font>各自維護本身的行爲,通知調用中介者。<font color="red">中介者</font>維護對象關係以及對象方法的調用。<font color="red">優勢</font>:對象的關係在中介者中能夠自由定義、一目瞭然。減小了兩個類的臃腫。去除了兩個對象之間的緊耦合。<font color="red">缺點</font>:兩個關係類的不臃腫換來了<font color="red">中介者</font>類的臃腫。

6、裝飾者模式

<font size="4" color="red">爲對象增添特性的技術,它並不使用建立新子類這種手段</font>

場景分析:看了別的文章都是自行車的場景那我也來唄,嘻嘻~,自行車商店:出售A、B、C、D四種牌子的自行車,這時候能夠選擇配件車燈、前置籃。若是按照正常創造子類的方法,咱們首先定義 A、B、C、D四個牌子的自行車父類,而後再根據配件經過繼承父類來實現子類。

// A自行車父類
class Bicycle{
    constructor(){
        this.type = 'A'
    }
    getBicycle(){
        console.log('A自行車的售價100')
    }
}
// 擁有車燈的A類自行車類
class BicycleDeng extends Bicycle{
    constructor(){
        super()
    }
    setDeng(){
        console.log('我安裝上了大燈')
    }
}

上述代碼咱們能夠發現:當咱們須要窮盡場景中可能出現的自行車的時候:A類有車燈、A類有前置欄、B類有車燈....一共要4*2一共八個類。這個時候咱們使用的是<font color="red">建立新子類</font>的手段將全部狀況窮舉到<font color="red">實體類</font>上面,想要實例化一個對象只要找到對應實體類就行了。

優化:若是自行車的類型增多,配件增多就會出現實體類幾何增長,大家應該不會想把本身的餘生花在維護實體類上吧,來吧出來吧咱們的<font color="red">裝飾者模式</font>。首先回歸業務場景:A、B、C、D咱們能夠暫時稱爲<font color="red">組件</font>。而咱們的配件就是<font color="red">裝飾者</font>。<font color="red">主要思路</font>:替換建立新子類,每一個裝飾者維護單個裝飾者類,將<font color="red">組件做爲實例傳入裝飾者類中進行‘裝飾’</font>能夠重寫原有方法、也能夠新增方法。 這樣咱們只要維護組件類加裝飾者類 4+2一個6個類

class Bicycle{
    constructor(){
        this.type = 'A'
        // 自行車售價
        this.price = 5000
    }
    getBicycle(){
        console.log(`A自行車的售價${this.price}`)
    }
}
class BicycleDecorator{
    constructor(bicycle){
        // 傳入的組件
        this.bicycle = bicycle
        // 大燈售價
        this.dengPrice = 200
    }
    // 新增方法
    openDeng(){
        console.log('我打開了燈')
    }
    // 重寫方法
    getBicycle(){
        console.log(`A自行車的售價${this.bicycle.price + this.dengPrice}`)
    }
}
// 先建立類型自行車
var a = new Bicycle()
// 裝飾後替換原有
a = new BicycleDecorator(a)
a.getBicycle() // A自行車的售價5200

總結:<font color="red">裝飾者模式</font>爲對象增添特性提供了新的思路,去除了建立新的子類對應最小單位實體類,經過傳遞一個父類來進行對於父類增長新特性的方法是,保留了父類原有方法也具備延展性。真香~~

結束語

本文提供了部分的設計模式以及本身的理解,理解可能存在誤差歡迎討論,本文參考的是《javascript設計模式》。以後若有設計模式的補充還會繼續更新。

相關文章
相關標籤/搜索