什麼是裝飾器模式?
裝飾器模式(Decorator Pattern)允許向一個現有的對象添加新的功能,同時又不改變其結構。這種類型的設計模式屬於結構型模式,它是作爲現有的類的一個包裝。
實例
拿最近比較火的吃雞遊戲(絕地求生:大逃殺PUBG)來說,遊戲中每個玩家降落到島上,剛開始是一無所有的,需要通過撿拾或掠奪裝備來武裝自己,然後經過互相殘酷的拼殺,獲得遊戲的勝利。
遊戲過程中,我們可以把每一個玩家當成需要裝飾的主類。其餘的武器當成裝飾類。玩家可以被任何武器裝飾,從而獲得不同的能力。
下面例子中,玩家主類分別通過手槍類和狙擊步槍(Kar98)類修飾後,強化了自身的 fire 方法。獲取了不一樣的功能。與此同時,類裏的其他方法 sayName 並沒有受到裝飾器的影響。
// 被裝飾的玩家
class Player {
constructor(name) {
this.name = name
}
sayName() {
console.log(`I am ${this.name}`)
}
fire() {
console.log('I can only punch!')
}
}
// 裝飾器——手槍
class Pistol {
constructor(player) {
player.fire = this.fire
}
fire() {
console.log('I shoot with my Pistol!')
}
}
//裝飾器——Kar98狙擊步槍
class Kar98 {
constructor(player) {
player.fire = this.fire
}
fire() {
console.log('I shoot with my Kar98!')
}
}
// 新玩家
const player = new Player('zkk')
//打招呼
player.sayName() // => 'I am zkk'
// 現在還沒有武器,只會用拳頭
player.fire() // => 'I can only punch!'
// 哎,撿到一個手槍,裝飾上
const playerWithPistol = new Pistol(player)
// 發現敵人,用手槍開火
playerWithPistol.fire() // => 'I shoot with my Pistol!'
// 哇!撿到一個98K,裝飾上
const playerWithKar98 = new Kar98(player)
// 用98k開火,奈斯!
playerWithKar98.fire() // => 'I shoot with my Kar98!'
通過實例,我們可以看出裝飾器模式可以動態地給一個對象添加一些額外的功能,同時結構上更加靈活。
如果不使用裝飾者模式,爲了實現以上功能,我們就需要對玩家和各種武器的組合創建無數多的類,在需要的時候再去實例化。這樣的管理是非常複雜的。
優點缺點
優點
- 裝飾類和被裝飾類可以獨立發展,不會相互耦合
- 裝飾模式是繼承的一個替代模式
- 裝飾模式可以動態擴展一個實現類的功能,而不必擔心影響實現類
缺點
- 如果管理不當會極大增加系統複雜度
- 多層裝飾比較複雜
- 不熟悉這個模式的開發人員難以理解
擴展
目前 TS 和 ES7 已經支持裝飾器的使用,下面是新語法中方法裝飾器的使用。
方法裝飾器表達式會在運行時當作函數被調用,傳入下列3個參數:
- target——對於靜態成員來說是類的構造函數,對於實例成員是類的原型對象。
- name——成員的名字。
- descriptor——成員的屬性描述符。
// 定義kar98方法裝飾器
let kar98 = (target, name, descriptor) => {
// 裝飾器中替代原先的fire方法
descriptor.value = function () {
target.sayName()
console.log(`${name}功能被增強`)
console.log('I can fire with kar98!');
}
}
// 玩家類
class Player {
sayName() {
console.log('I am zkk')
}
// 用kar98裝飾器裝飾fire方法,就可以升級能力了。
@kar98
fire(){
// 默認如果沒有裝飾器,只能拳擊
console.log('I can punch!')
}
}
const player = new Player()
player.fire()
輸出:
除了方法裝飾器,還有類裝飾器,屬性裝飾器,參數裝飾器等等。使用詳情可以參照 TS 使用手冊裝飾器一節