js設計模式筆記 - 裝飾者模式

從實例中入手

咱們就拿《head first 設計模式》中的一個例子入手,使用面向對象的編程思想:html

咱們有一家咖啡店,暫定每杯咖啡售價10元
function Coffee() {
    this.price = 10
}

Coffee.prototype.getPrice = function () {
    return this.price
}

var coffee = new Coffee()
console.log(coffee.getPrice());
如今新進了一批配料(摩卡,藍莓,奶泡)分別對應價錢爲(5,3,4),那麼咱們要怎麼計算一杯摩卡咖啡的價錢呢?

1.新建一個摩卡咖啡的對象:編程

function MochaCoffee() {
    this.price = 15
}
MochaCoffee.prototype.getPrice = function () {
    return this.price
}
var mochaCoffee = new MochaCoffee()
console.log(mochaCoffee.getPrice());

2.使用繼承設計模式

var coffee = new Coffee()

function MochaCoffee() {
    this.mochaPrice = 5
}
MochaCoffee.prototype = new Coffee()
MochaCoffee.prototype.getPrice = function () {
    return this.price + this.mochaPrice
}
var mochaCoffee = new MochaCoffee()
console.log(mochaCoffee.getPrice());

那麼每增長一種配料,就須要生成多一個類。app

可是,不管是第一種辦法,仍是第二種辦法都很難知足一下的狀況,好比函數

  1. 若是我須要一杯摩卡加藍莓的咖啡,那麼只能再生成一個新的類(MochaBlueberryCoffee)
  2. 若是我須要一杯兩倍藍莓的咖啡呢? 難道要生成一個新的類叫(DoubelMochaBlueberryCoffee)嗎?

目前只有3種配料,咱們就須要添加3個新的類了,那麼當咱們的配料添加,或者添加(咖啡根據大中小杯去計算價錢)這種需求時,就須要添加無數的類去實現它;性能

若是隻是使用繼承,那麼當狀況複雜時,類的數量就會呈爆炸式增加,這是咱們所不但願見到的。this


裝飾者模式登場

使用構造器存儲coffee對象的引用prototype

function Mocha(coffee) {
    this.price = 5
    this.coffee = coffee
}
Mocha.prototype.getPrice = function () {
    return this.price + this.coffee.getPrice()
}

var coffee = new Coffee()
var mochaCoffee = new Mocha(coffee)
console.log(mochaCoffee.getPrice());

若是這時咱們須要點一份雙倍摩卡的咖啡,直接以下調用:設計

var coffee = new Coffee()
var mochaCoffee = new Mocha(coffee)
var doubelMochaCoffee = new Mocha(mochaCoffee)
console.log(doubelMochaCoffee.getPrice());

使構造器存儲coffee對象的引用一點也不靈活,咱們作以下改動:代理

function Mocha() {
    this.price = 5
}
Mocha.prototype.getPrice = function () {
    return this.price + this.coffee.getPrice()
}
Mocha.prototype.setCoffee = function (coffee) {
    this.coffee = coffee
}

var coffee = new Coffee()
var mochaCoffee = new Mocha()
mochaCoffee.setCoffee(coffee)
console.log(mochaCoffee.getPrice());

如今就能夠動態的設置coffee對象了!

若是你還想要一杯摩卡藍莓咖啡也是很是容易實現的:

function Blueberry() {
    this.price = 3
}
Blueberry.prototype.getPrice = function () {
    return this.price + this.coffee.getPrice()
}
Blueberry.prototype.setCoffee = function (coffee) {
    this.coffee = coffee
}

var coffee = new Coffee()
var mochaCoffee = new Mocha()
mochaCoffee.setCoffee(coffee)
var mochaBlueberryCoffee = new Blueberry()
mochaBlueberryCoffee.setCoffee(mochaCoffee)
console.log(mochaBlueberryCoffee.getPrice());

簡單應用

window.onload = function () {
    alert(1)
}

var _onload = window.onload

window.onload = function () {
    _onload()
    alert('new')
}

這是實際開發中很常見的一種作法,好比咱們想給 window 綁定 onload 事件,可是又不肯定 這個事件是否是已經被其餘人綁定過,爲了不覆蓋掉以前的 window.onload 函數中的行爲,我 們通常都會先保存好原先的 window.onload,把它放入新的 window.onload 裏執行,添加了一層簡單的包裝。

可是上面的作法會致使this丟失的問題,這裏不會報錯是由於全局變量就是window;


使用AOP裝飾函數

分別實現Function.prototype.beforeFunction.prototype.after,以下:

Function.prototype.before = function (beforefn) {
    var __self = this; // 保存原函數的引用
    return function () { // 返回包含了原函數和新函數的"代理"函數
        beforefn.apply(this, arguments); // 執行新函數,且保證 this 不被劫持,新函數接受的參數 // 也會被原封不動地傳入原函數,新函數在原函數以前執行
        return __self.apply(this, arguments); // 執行原函數並返回原函數的執行結果, 2 // 而且保證 this 不被劫持
    }
}
Function.prototype.after = function (afterfn) {
    var __self = this;
    return function () {
        var ret = __self.apply(this, arguments); afterfn.apply(this, arguments);
        return ret;
    }
};

在實際中的應用:

數據統計上報功能:

<html>
    <button tag="login" id="button">點擊打開登陸浮層</button> 
<script>
    var showLogin = function(){ 
        console.log( '打開登陸浮層' ); 
        log('上報信息');
    }
    var log = function( tag ){
        console.log( '上報信息爲: ' + tag );
    }
    document.getElementById( 'button' ).onclick = showLogin;
</script> 
</html>

咱們看到在 showLogin 函數裏,既要負責打開登陸浮層,又要負責數據上報,這是兩個層面 的功能,在此處卻被耦合在一個函數裏。使用 AOP 分離以後,代碼以下:

<html>
    <button tag="login" id="button">點擊打開登陸浮層</button> 
<script>
    Function.prototype.after = function( afterfn ){ 
        var __self = this;
        return function(){
            var ret = __self.apply( this, arguments ); 9 afterfn.apply( this, arguments );
            return ret;
        } 
    };
    var showLogin = function(){ 
        console.log( '打開登陸浮層' );
    }
    var log = function(){
        console.log( '上報信息爲: ' + 'balabalabla' );
    }
    showLogin = showLogin.after( log ); // 打開登陸浮層以後上報數據
    document.getElementById( 'button' ).onclick = showLogin;
</script> 
</html>

本質

在《設計模式》成書以前,GoF原想把裝飾者(decorator)模式稱爲包裝器(wrapper)模式。
其實裝飾者模式,就是在原有的基礎上添加了一層包裝,造成一條包裝鏈。
使用了裝飾者模式,咱們能夠動態的添加被裝飾者的職責,對它進行非入侵式的行爲修改,從而達到咱們要求而不須要建立更多的類。

缺點:當裝飾的層數過深時,實際上做用域鏈也就變的很長,性能也會受到影響。

參考資料

  • 《head first 設計模式》
  • 《JavaScript設計模式與實踐》
  • 《JavaScript設計模式》
相關文章
相關標籤/搜索