什麼是裝飾器模式
裝飾器模式(Decorator Pattern)允許向一個現有的對象添加新的功能,同時又不改變其結構。
裝飾器模式的定義就比較直白啦,就是對我們現有的一個類去添加了一個新的功能,但是呢,新的功能並不會改變這個類原先的結構。那怎麼做呢?所以我們就需要去添加一個新的類,通過這個新的類來去爲原有的類增加功能,這個新的類就是一個裝飾類,也就是裝飾器,這樣一種模式,就是裝飾器模式。
舉例說明
知道了裝飾器模式的定義之後,我們來看舉例說明,裝飾器模式的例子也很貼近生活,就比如我們的手機殼。
像這種手機殼,他並沒有改變我們手機原有的功能,比如打電話,聽音樂什麼的。但卻爲手機提供了新的功能,比如後面的指環,這就是提供的新的功能。這就是一個典型的裝飾器模式在生活中的例子。
繪製UML類圖
然後我們就根據這個手機殼的實例,來繪製UML
類圖。
我們看一下繪製出來的UML類圖
,首先是Client
代表是我們,我們有一個手機Phone
,然後有一個手機殼作爲手機的裝飾器,我們叫他Decorator
,這個main
方法是一個入口函數,可以理解爲我們對手機進行了一個打電話的操作,或者是對手機進行了一個使用的操作都可以。
然後手機擁有一個call
方法,表示這個手機具有打電話的功能,然後裝飾器Decorator
它持有了一個手機的引用,所以它也具備了一個打電話的功能, 但是這個功能我們要知道它是藉助手機Phone
來完成的, 這個一定注意, 裝飾器本身並沒有被裝飾類
的功能,它是因爲持有了被裝飾類
的引用,所以才具備了被裝飾類的功能
,其實這個功能還是通過被裝飾類
來完成的。 然後我們的裝飾器對手機提供了一個新的功能antiFall
,藉助antiFall
我們的手機具備了防止墜落的功能。
梳理完UML類圖
的邏輯之後,我們來看一下上面的這些內容,我們如何通過代碼去實現。
代碼實現
class Phone {
call () {
console.log('打電話');
}
}
class Decorator {
constructor (phone) {
this.phone = phone;
}
call () {
this.antiFall();
this.phone.call();
}
antiFall () {
console.log('防止墜落');
}
}
class Client {
constructor () {
const phone = new Phone();
this.decorator = new Decorator(phone);
}
main () {
this.decorator.call();
}
}
const client = new Client();
client.main();
我們來分析一下上面的代碼,首先Phone
具有一個打電話的功能call
,當我們執行call
方法的時候,會打印打電話
這三字,然後有一個裝飾器類Decorator
,它持有Phone
的引用,藉助Phone
,Decorator
也擁有的打電話的功能call
,並且又提供了一個附加功能antiFall
,當我們執行Decorator
的call
方法的時候,它會調用Phone
的call
方法,完成打電話的功能,並會調用它自己的antiFall
方法完成它本身防止墜落的功能。
最後我們通過Client
來初始化了Phone
和Decorator
,並調用antiFall
方法。最終打印的結果爲:
防止墜落
打電話
這樣我們就通過Decorator
來爲Phone
裝飾上了一個新的功能。
使用場景
關於裝飾器的使用場景,其最好的解釋就是ES7
中Decorator
提案了,關於這塊內容,阮一峯老師早在這裏就做了詳細的解釋,我們在這裏就不在去做一遍重複了,不過如果我們想要去使用 修飾器語法@Decorator
(我們後面會使用@Decorator
來表示修飾器語法) 的話,那麼還需要額外做一些操作,這一塊內容阮一峯老師並沒有說,那麼我們就在這裏把使用@Decorator
的準備工作說一下,並且通過這個新的修飾器語法來把我們上面的項目進行一個改造,如果大家想要對@Decorator
語法進行更深的瞭解,那麼可以點擊這裏。
因爲@Decorator
爲ES7
提案所以對想在的瀏覽器來說,絕大多數並不兼容,所以我們就需要使用babel
對我們的代碼進行一個轉義。那麼我們先通過npm
來安裝一下babel
,我們執行 npm install --save-dev @babel/core babel-cli babel-preset-es2015
, 然後如果我們想要babel
能夠識別@Decorator
我們還需要安裝babel-plugin-transform-decorators-legacy
,我們執行npm install --save-dev babel-plugin-transform-decorators-legacy
,最終我們的package.json
的配置如下:
{
"devDependencies": {
"@babel/core": "^7.1.6",
"babel-cli": "^6.26.0",
"babel-plugin-transform-decorators-legacy": "^1.3.5",
"babel-preset-es2015": "^6.24.1"
}
}
然後我們創建一個.babelrc
的文件,用以對babel
完成基礎配置,配置內容如下:
{
"presets": [
"es2015"
],
"plugins": [
"transform-decorators-legacy"
]
}
這些配置操作都完成之後,我們就是用@Decorator
來重構一下我們的實例代碼,我們在項目根目錄下創建decorator.js
,此時我們的項目目錄如下:
- node_modules
- index.html
- .babelrc
- package-lock.json
- package.json
- decorator.js
然後我們來寫一下decorator.js
中的代碼,使用@Decorator
來重構我們的實例:
class Phone {
@antiFall
call () {
console.log('打電話');
}
}
function antiFall () {
console.log('防止墜落');
}
let phone = new Phone();
phone.call();
在上面的代碼中,我們就對call
方法進行了一個裝飾,裝飾的方法就是antiFall
,至於上面的代碼什麼意思,我就不再這裏說了,我強烈推薦大家去看阮一峯老師關於ES7修飾器的文章。
然後我們可以通過npx babel decorator.js -o build.js
來把decorator.js
編譯成build.js
,然後在index.html
中引入build.js
,代碼的執行結果應該爲:
防止墜落
打電話
總結
進入總結部分,裝飾器我們會在什麼情況下使用呢? 如果大家聽了我的去看了阮一峯老師的文章,那麼就應該直達了core-decorators這個第三方類庫了哈,其實這個類庫就是一個很經典的裝飾器使用方式。
首先裝飾器模式不會改變被裝飾類的現有結構。
其次裝飾器模式是對被裝飾類的現有功能的一個升級。
最後裝飾器模式可以對被裝飾類進行提供額外的註釋功能。