模板方法模式

模板方法模式是一種只需使用繼承就可以實現的非常簡單的模式。
模板方法模式由兩部分結構組成,第一部分是抽象父類,第二部分是具體的實現子類。通常在抽象父類中封裝了子類的算法框架,包括實現一些公共方法以及封裝子類中所有方法的執行順序。子類通過繼承這個抽象類,也繼承了整個算法結構,並且可以選擇重寫父類的方法。
假如我們有一些平行的子類,各個子類之間有一些相同的行爲,也有一些不同的行爲。如果相同和不同的行爲都混合在各個子類的實現中,說明這些相同的行爲會在各個子類中重複出現。但實際上,相同的行爲可以被搬移到另外一個單一的地方,模板方法模式就是爲解決這個問題而生的。在模板方法模式中,子類實現中的相同部分被上移到父類中,而將不同的部分留待子類來實現。這也很好地體現了泛化的思想。

基於繼承實現一個模板方法模式

const Beverage=function(){}

Beverage.prototype.boilWater=function(){
  console.log('把水煮沸')
}

Beverage.prototype.brew=function(){
  throw new Error ('子類必須重寫brew方法')
}

Beverage.prototype.pourInCup=function(){
  throw new Error ('子類必須重寫pourInCup方法')
}

Beverage.prototype.addCondiments=function(){
  throw new Error('子類必須重寫addCondiments方法')
}

Beverage.prototype.customerWantsCondiments=function(){
  return true
}

Beverage.prototype.init=function(){
  this.boilWater()
  this.brew()
  this.pourInCup()
  if(this.customerWantsCondiments()){
    this.addCondiments()
  }
}

const CoffeeWithHook=function(){}

CoffeeWithHook.prototype=new Beverage()

CoffeeWithHook.prototype.brew=function(){
  console.log('用沸水沖泡咖啡')
}

CoffeeWithHook.prototype.pourInCup=function(){
  console.log('把咖啡倒進杯子')
}

CoffeeWithHook.prototype.addCondiments=function(){
  console.log('加糖和牛奶')
}

CoffeeWithHook.prototype.customerWantsCondiments=function(){
  return window.confirm('請問需要調料嗎?')
}

let coffeeWithHook=new CoffeeWithHook()
coffeeWithHook.init()

其中的customerWantsCondiments是鉤子方法,子類可以通過自己定義此方法來控制父類的行爲,當然這個鉤子需要時父類預設好的。

好萊塢原則

好萊塢無疑是演員的天堂,但好萊塢也有很多找不到工作的新人演員,許多新人演員在好萊塢把簡歷遞給演藝公司之後就只有回家等待電話。有時候該演員等得不耐煩了,給演藝公司打電話詢問情況,演藝公司往往這樣回答:“不要來找我,我會給你打電話。”

在設計中,這樣的規則就稱爲好萊塢原則。在這一原則的指導下,我們允許底層組件將自己掛鉤到高層組件中,而高層組件會決定什麼時候、以何種方式去使用這些底層組件,高層組件對待底層組件的方式,跟演藝公司對待新人演員一樣,都是“別調用我們,我們會調用你”。

模板方法模式是好萊塢原則的一個典型使用場景,它與好萊塢原則的聯繫非常明顯,當我們用模板方法模式編寫一個程序時,就意味着子類放棄了對自己的控制權,而是改爲父類通知子類,哪些方法應該在什麼時候被調用。作爲子類,只負責提供一些設計上的細節。

除此之外,好萊塢原則還常常應用於其他模式和場景,例如發佈-訂閱模式和回調函數。

  • 發佈—訂閱模式
    在發佈—訂閱模式中,發佈者會把消息推送給訂閱者,這取代了原先不斷去fetch消息的形式。例如假設我們乘坐出租車去一個不瞭解的地方,除了每過5秒鐘就問司機“是否到達目的地”之外,還可以在車上美美地睡上一覺,然後跟司機說好,等目的地到了就叫醒你。這也相當於好萊塢原則中提到的“別調用我們,我們會調用你”。

  • 回調函數
    在ajax異步請求中,由於不知道請求返回的具體時間,而通過輪詢去判斷是否返回數據,這顯然是不理智的行爲。所以我們通常會把接下來的操作放在回調函數中,傳入發起ajax異步請求的函數。當數據返回之後,這個回調函數才被執行,這也是好萊塢原則的一種體現。把需要執行的操作封裝在回調函數裏,然後把主動權交給另外一個函數。至於回調函數什麼時候被執行,則是另外一個函數控制的。
    javascript高階函數實現模板方法模式

const Beverage=function(){
  const boilWater=function(){
    console.log('把水煮沸')
  }

  const brew=params.brew||function(){
    throw new Error('必須傳遞brew方法')
  }

  const pourInCup=parasm.pourInCup||function(){
    throw new Error('必須傳遞pourInCup方法')
  }

  const addCondiments=params.addCondiments||function(){
    throw new Error('必須傳遞addCondiments方法')
  }

  const F=function(){}

  F.prototype.init=function(){
    boilWater()
    brew()
    pourInCup()
    addCondiments()
  }

  return F
}

const Coffee=Beverage({
  brew:function(){
    console.log('用沸水沖泡咖啡')
  },
  pourInCup:function(){
    console.log('把咖啡倒進杯子')
  },
  addCondiments:function(){
    console.log('加糖和牛奶')
  }
})
const Tea=Beverage({
  brew:function(){
    console.log('用沸水沖泡茶葉')
  },
  pourInCup:function(){
    console.log('把茶倒進杯子')
  },
  addCondiments:function(){
    console.log('加檸檬')
  }
})

const coffee=new Coffee()
coffee.init()

const tea=new Tea()
tea.init()

模板方法模式是一種典型的通過封裝變化提高系統擴展性的設計模式。在傳統的面嚮對象語言中,一個運用了模板方法模式的程序中,子類的方法種類和執行順序都是不變的,所以我們把這部分邏輯抽象到父類的模板方法裏面。而子類的方法具體怎麼實現則是可變的,於是我們把這部分變化的邏輯封裝到子類中。通過增加新的子類,我們便能給系統增加新的功能,並不需要改動抽象父類以及其他子類,這也是符合開放-封閉原則的。

但在JavaScript中,我們很多時候都不需要依樣畫瓢地去實現一個模版方法模式,高階函數是更好的選擇。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章