《JavaScript設計模式與開發實踐》模式篇(12)—— 裝飾者模式

嗨呀豆豆呢發表於2018-12-24

在傳統的面嚮物件語言中,給物件新增功能常常使用繼承的方式,但是繼承的方式並不靈活, 還會帶來許多問題:一方面會導致超類和子類之間存在強耦合性,當超類改變時,子類也會隨之 改變;另一方面,繼承這種功能複用方式通常被稱為“白箱複用”,“白箱”是相對可見性而言的, 在繼承方式中,超類的內部細節是對子類可見的,繼承常常被認為破壞了封裝性。裝飾者模式能夠在不改變物件自身的基礎上,在程式執行期間給物件動態地新增職責。跟繼承相比,裝飾者是一種更輕便靈活的做法,這是一種“即用即付”的方式

故事背景

假設我們在編寫一個飛機大戰的遊戲,隨著經驗值的增加,我們操作的飛機物件可以升級成更厲害的飛機,一開始這些飛機只能發射普通的子彈,升到第二級時可以發射導彈,升到第三級時可以發射原子彈。

程式碼實現(使用裝飾者模式)

var plane = {
    fire: function(){
        console.log( '發射普通子彈' ); 
    }
}
var missileDecorator = function(){ 
    console.log( '發射導彈' );
}
var atomDecorator = function(){ 
    console.log( '發射原子彈' );
}
var fire1 = plane.fire;
plane.fire = function(){ 
    fire1();
    missileDecorator(); 
}
var fire2 = plane.fire;
plane.fire = function(){ 
    fire2();
    atomDecorator(); 
}
plane.fire();
// 分別輸出: 發射普通子彈、發射導彈、發射原子彈
複製程式碼

使用AOP實現裝飾者模式

首先給出 Function.prototype.before 方法和 Function.prototype.after 方法

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

AOP 的應用例項

  • 資料統計上報 比如頁面中有一個登入 button,點選這個 button 會彈出登入浮層,與此同時要進行資料上報, 來統計有多少使用者點選了這個登入 button
    • 未使用AOP
    var showLogin = function(){ 
       console.log( '開啟登入浮層' ); 
       log( this.getAttribute( 'tag' ) );
    }
    var log = function( tag ){
       console.log( '上報標籤為: ' + tag );
       (new Image).src = 'http:// xxx.com/report?tag=' + tag;
    }
    document.getElementById( 'button' ).onclick = showLogin;
    複製程式碼
    • 使用AOP
    var showLogin = function(){ 
        console.log( '開啟登入浮層' );
    }
    var log = function(){
        console.log( '上報標籤為: ' + this.getAttribute( 'tag' ) );
    }
    showLogin = showLogin.after( log ); // 開啟登入浮層之後上報資料
    document.getElementById( 'button' ).onclick = showLogin;
    複製程式碼
  • 外掛式的表單驗證 我們很多人都寫過許多表單驗證的程式碼,在一個 Web 專案中,可能存在非常多的表單,如 註冊、登入、修改使用者資訊等。在表單資料提交給後臺之前,常常要做一些校驗,比如登入的時 候需要驗證使用者名稱和密碼是否為空
    • 未使用AOP
    var formSubmit = function(){
        if ( username.value === '' ){
            return alert ( '使用者名稱不能為空' ); 
        }
        if ( password.value === '' ){
            return alert ( '密碼不能為空' );
        }
        var param = {
            username: username.value, password: password.value
        }
        ajax( 'http:// xxx.com/login', param );
    }
    submitBtn.onclick = function(){ 
        formSubmit();
    }
    複製程式碼
    • 使用AOP
    var validata = function(){
        if ( username.value === '' ){
            alert ( '使用者名稱不能為空' );
            return false; 
        }
        if ( password.value === '' ){ 
            alert ( '密碼不能為空' ); 
            return false;
        } 
    }
    var formSubmit = function(){ 
        var param = {
            username: username.value,
            password:password.value
        }
        ajax( 'http:// xxx.com/login', param ); 
    }
    formSubmit = formSubmit.before( validata );
    submitBtn.onclick = function(){ 
        formSubmit();
    }
    複製程式碼

小結

裝飾者模式和代理模式的結構看起來非常相像,這兩種模式都描述了怎樣為物件提供 一定程度上的間接引用,它們的實現部分都保留了對另外一個物件的引用,並且向那個物件傳送 請求。 代理模式和裝飾者模式最重要的區別在於它們的意圖和設計目的。代理模式的目的是,當直接訪問本體不方便或者不符合需要時,為這個本體提供一個替代者。本體定義了關鍵功能,而代理提供或拒絕對它的訪問,或者在訪問本體之前做一些額外的事情。裝飾者模式的作用就是為對 象動態加入行為。

系列文章:

《JavaScript設計模式與開發實踐》最全知識點彙總大全

相關文章