JavaScript設計模式詳解:06、裝飾器模式

什麼是裝飾器模式

裝飾器模式(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的引用,藉助PhoneDecorator也擁有的打電話的功能call,並且又提供了一個附加功能antiFall,當我們執行Decoratorcall方法的時候,它會調用Phonecall方法,完成打電話的功能,並會調用它自己的antiFall方法完成它本身防止墜落的功能。

最後我們通過Client來初始化了PhoneDecorator,並調用antiFall方法。最終打印的結果爲:

防止墜落
打電話

這樣我們就通過Decorator來爲Phone裝飾上了一個新的功能。

使用場景

關於裝飾器的使用場景,其最好的解釋就是ES7Decorator 提案了,關於這塊內容,阮一峯老師早在這裏就做了詳細的解釋,我們在這裏就不在去做一遍重複了,不過如果我們想要去使用 修飾器語法@Decorator (我們後面會使用@Decorator來表示修飾器語法) 的話,那麼還需要額外做一些操作,這一塊內容阮一峯老師並沒有說,那麼我們就在這裏把使用@Decorator的準備工作說一下,並且通過這個新的修飾器語法來把我們上面的項目進行一個改造,如果大家想要對@Decorator語法進行更深的瞭解,那麼可以點擊這裏

因爲@DecoratorES7提案所以對想在的瀏覽器來說,絕大多數並不兼容,所以我們就需要使用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這個第三方類庫了哈,其實這個類庫就是一個很經典的裝飾器使用方式。

首先裝飾器模式不會改變被裝飾類的現有結構。
其次裝飾器模式是對被裝飾類的現有功能的一個升級。
最後裝飾器模式可以對被裝飾類進行提供額外的註釋功能。

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