學學AOP之裝飾者模式

裝飾者,英文名叫decorator. 所謂的"裝飾",從字面能夠很容易的理解出,就是給 土肥圓,化個妝,華麗的轉身爲白富美,但本體仍是土肥圓。 ajax

說人話.
咳咳~編程

在js裏面一切都是對象,並且函數就是一等對象。 在普通的Object中,咱們能夠很容易的添加屬性或者其餘方法,固然函數也不例外。 可是,這樣作的後果就是,咱們會不斷的改變本體,就像把鳳姐送去作整形手術同樣。 固然,結果有好有壞,也許鳳姐就是下一個angelababy,也許... 因此,爲了咱們代碼的純潔度(就算你是醜小鴨), 咱們能夠不動刀子的狀況下,讓你變得又白又美。安全

引用裝飾

這個是裝飾的初級階段,就是抹點粉而已。 在js中,咱們叫作引用裝飾。app

talk is cheap, show u code框架

//咱們給jimmy函數額外添加其餘的功能
var jimmy = function(){
    console.log("jimmy");
}
var _jimmy = jimmy;
jimmy = function(){
    _jimmy();
    console.log("I love HuaHua");
}
jimmy();

這個的應用場景主要就是在多人協做和框架設計裏面。好比,李冰岩已經使用了onload函數,可是,小舟又想使用onload函數。 這樣會形成一個問題,若是小舟直接改動的話,他須要看的是李冰岩寫的蜜汁代碼,並且還要防止不會引發錯誤。這無疑是很困難的,因此在這裏,可使用引用裝飾,來給onload在添加一層。函數

//這是小李的蜜汁代碼
var xiaoLi = function(){
    console.log("蜜汁代碼");
}
window.onload = xiaoLi;  //小李已經綁定好onload函數了
//接下來小舟須要改動onload代碼
var fn = window.onload;
var xiaoZhou = function(){
    fn();
    conosle.log("整潔代碼");
}
window.onload = function(){  //根據onload的特性,直接覆蓋掉小李的code
    xiaoZhou();
}

因此引用裝飾的用處仍是蠻大的。
大你妹啊~~
啊。。。。
(另外一Me) 咱們來分析一下,上面那個引用模式有什麼弊端(好處已經都知道了).
首先,咱們使用引用模式的時候,一定會添加一個多餘的引用對象,好比上文的"fn".
並且隨着你程序鏈的增長,中間對象必定會和你節點同等數量的。固然,起名我就不說了,關鍵是,一大堆無用的代碼放在那裏,感受很不爽的。 因此,這裏引入AOP的裝飾模式.工具

AOP裝飾

親切,熟悉,完美。 咱們見過AOP應該不止一次了,在職責鏈模式使用過,在迭代器模式使用過等等。使用這麼屢次,好像尚未對AOP作一些基本解釋呢?因此,這裏給你們咻咻.
AOP中文名叫面向切面編程。 先說一下這個名詞,「面向」這詞應該不用解釋,關鍵"切面"是什麼鬼。 若是你們作過sangwich,應該就知道,首先咱們買來一塊麪包,須要將麪包切開,而後在切面上面加上一些flavoring,好比蔬菜,火腿,培根之類的。 恩,對比js程序來講,一個程序鏈就至關於你買回來的麪包,flavoring就是你想加的功能函數,如何將函數正確的放置在程序鏈中合適的位置,這就是AOP作的事情。
首先,再次將兩個動態函數咻咻:性能

Function.prototype.after = function(fn){
    var _this = this;
    return function(){
        var res = _this.apply(this,arguments);
        fn.apply(this,arguments);
        return res;
    }
}
Function.prototype.before = function(fn){
    var _this = this;
    return function(){
        fn.apply(this,arguments);  
        return    _this.apply(this,arguments);
    }
}

這兩個函數的組合構成了js中AOP模式的精華.而AOP最經常使用的就是講與業務邏輯無關的功能動態織入到主程序中。測試

talk is cheap , show u codethis

舉個栗子吧: 使用AOP計算程序運行事件

//純手寫計算函數運行事件
function factorial(n) {  //最基本的階乘計算
  if (n === 1) return 1;
  return n * factorial(n - 1);
}
function calTime(n){
    var start = new Date().getMilliseconds();
    factorial(n);
    console.log(new Date().getMilliseconds() - start+"ms");
}
calTime(1000);

能夠得出耗費的時間,可是,若是還有其餘的函數須要測試,那麼這麼作的意義並無很大的價值。咱們使用AOP進行重構。

function factorial(n) {  //最基本的階乘計算
  if (n === 1) return 1;
  return n * factorial(n - 1);
}
var calTime = (function(){
    var start;
    return    function(){
        if(!start){  //給開始時間賦值
            start = new Date().getMilliseconds();
        }else{
            console.log(new Date().getMilliseconds()-start+"ms");
            start = undefined;
        }
    }
})();
var calcu = factorial.before(calTime).after(calTime)(200);

這樣很好的將計時功能從業務邏輯中提取出來,並且看着真的頗有angelababy的味道誒.
使用AOP的時候須要注意一點就是,before&after執行完後,返回的結果都是第一個函數的內容。

var result = function(){
    return 1;
}.before(function(){
    return 2;
}).after(function(){
    return 3;
});
console.log(result());  //1

咱們大體的瞭解了AOP的用法和理論,能夠針對於開頭所說的例子進行重構.

window.onload = function(){
    console.log("小李的蜜汁代碼");
}
var fn = window.onload;
fn.before(function(){
    console.log("整潔代碼");
});
window.onload = fn;

看起來,比上面那個栗子清晰不少,並且使用before和after也十分利於代碼的閱讀。

實例講解AOP裝飾

上面那個例子,只能算是AOP裝飾模式的一個不起眼的角落。 AOP引用的場景在js中,或者說在任何一門語言中都是大放光彩的。 在js中,"細粒度"對象是程序中複用性最高的對象,能把對象用細粒度的形式表示,那麼AOP無疑是最佳的選擇。
在寫業務邏輯的時候,咱們最大的問題就是判斷邏輯,使用大量的if語句,而這均可以通過思考巧妙化解。好比,我在寫購買課程的時候就會遇到一些邏輯。 只有當課程數目符合要求的時候,購買的效果纔能有效.
按正常的業務邏輯編寫

var buy = function(){
    if(confirm()){  //驗證購買信息是否合法
        http.buyCourse('xxx');  //發起請求
    }
}
var confirm = function(){
    console.log("驗證購買數量");
}
document.querySelector(".buy").addEventListener("click",function(){
    buy();
},false);

使用AOP裝飾模式重構後

var buy = function(){
    http.buyCourse("xxx"); //給後臺發起請求,驗證
}
var confirm = function(){
    console.log("驗證購買數量");
}
var buy = buy.before(confirm);
document.querySelector(".buy").addEventListener("click",function(){
    buy();
},false);

美美代碼的 滿滿即視感!!!
不夠,老闆,再來份糖炒栗子~
好嘞~
這裏咱們只是獲取函數的結果,那咱們想直接干預傳遞的參數,能夠嗎?
固然能夠。
咱們研究一下,before的內部構造(after是同樣的)

Function.prototype.before = function(fn){
    var _this = this;
    return function(){
        fn.apply(this,arguments);  
        return    _this.apply(this,arguments);
    }
}

這裏,因爲arguments是引用類型,若是fn改變了arguments,則會反映到_this.apply的arguments也會發生改變。 而這個應用場景就是,給ajax的地址添加上你須要的參數。
在實際項目中,一開始的接口,就是一個普普統統的地址,發請求,而後獲取參數。

http.ajax(url,type).then(...)

想這樣的使用,可是某天,你的leader提升了要求等級,將地址後面都加上一個token參數,或者說一個口令的md5或sha1的計算值,我想,這尼瑪工做量應該不小。
固然,咱們能夠直接將url進行傳遞。

var http = {
    ajax1(url){
        url += param.getToken();
        sendAjax(url);
    },
    ajax2(url){
        ...
    }
    ...
}

並且,萬一哪天你的leader說,哎,這樣作安全性仍是不過高,要不加兩個token混淆一下。
啊~啊~啊~啊~(混淆你妹啊,過不過年啦)
若是你繼續這麼寫,我相信,年終獎是有的,可是,春運火車票你估計是摸不着了。
因此可使用AOP進行動態織入。要知道,參數,我AOP也是能夠動的。

function dealUrl(url){
    url+=param.getToken();
}
http.ajax = http.ajax.before(dealUrl);
http.ajax("www.example.com");   //此時的Url = www.example.com?token=23jkfd3kjfdksjfkjds

並且,這個處理url函數,我也是能夠扔到任意一個js文件裏面複用的耶.
棒!!!
我AOP能夠動你要的參數,並且,我還能夠把個人結果給你是咻咻,若是我不讓你執行,你永遠也不會執行,哈哈哈哈~~~~
對不起,,上面那段是我意淫AOP說的。。。 其實AOP能夠算是萬能的配置工具,好比表單驗證吧。 咱們常常會把表單驗證和表單結果發送耦合在一塊兒。
像這樣

var sendRes = function(){
    if(user.userName === ""){
        alert("用戶名不能爲空~");
        return;
    }else if(user.password === ""){
        alert("密碼不能爲空~");
        return;
    }
    http.sendUser("xxx");  //驗證成功發送用戶信息
}

一個函數裏面同時含有了兩個職責,一個驗證一個發送用戶信息。 因此咱們如今的主要目的就是解耦。
回想一下,之前表單驗證咱們使用策略模式,解耦了驗證,這裏咱們再次使用。

var sendRes = function(){
    var detect = new Detect();  //策略者模式
    detect.add(user.userName,["notEmpty"]);
    detect.add(user.password,["notEmpty"]);
    var notPass = detect.getResult();
    if(notPass){  //若是沒經過
        console.log(notPass);
        return;
    }
    http.sendUser("xxx"); //驗證成功發送用戶信息
}

可使用上面那個驗證,可是結果是,驗證和策略模式仍是在一塊兒。咱們再使用AOP進行解耦。首先咱們得重構一下before函數

Function.prototype.before = function(fn){
    var _this = this;
    return function(){
        var res = fn.apply(this,arguments);  //值爲Boolean,表示是否繼續向下傳遞
        if(res==="next"){  //若是返回不成立,則繼續向下傳遞
            return    _this.apply(this,arguments);
        }
        return res;
    }
}

看到這裏,有些同窗應該明白是怎麼一回事了。沒錯,就是根據before裏面驗證的結果判斷是否執行下個發送請求的功能函數。
固然,若是不想污染原型,你也能夠自定義一個函數。

var before = function(beforeFn,fn){
    return function(){
        var res = beforeFn.apply(this,arguments);
        if(res==="next"){
            return fn.apply(this,arguments);
        }
    }
}

這樣寫也是能夠的。
咱們先按原型方式寫,這樣直觀一點

var sendRes = function(){
    http.sendUser("xxx"); //驗證成功發送用戶信息
}
sendRes = sendRes.before(function(){
    var detect = new Detect();  //策略者模式
    detect.add(user.userName,["notEmpty"]);
    detect.add(user.password,["notEmpty"]);
    var notPass = detect.getResult();
    if(notPass){  //若是沒經過
        console.log(notPass);
    }
    return "next";
});

能夠看出,驗證那部分已經徹底和發送用戶信息的功能函數徹底給解耦了。 這樣不只提升了函數的重用性,並且也讓你的代碼朝着「細粒度」方向大步前進.

辯證裝飾者模式

其實,裝飾者模式和職責鏈模式的形式是徹底同樣的,因此,他們的弊端也是相似的。鏈造的過長,對於性能來講就是一次rape.因此,仍是那句話,不要爲了模式而模式,沒有萬能的模式。

相關文章
相關標籤/搜索