聊Javascript中的AOP編程

Duck punch

咱們先不談AOP編程,先從duck punch編程談起。javascript

若是你去wikipedia中查找duck punch,你查閱到的應該是monkey patch這個詞條。根據解釋,Monkey patch這個詞來源於 guerrilla patch,意爲在運行中悄悄的改變代碼,而 guerrilla這個詞與 gorilla 同音,然後者意又與monkey相近(前者爲「猩猩」的意思),最後就演變爲了monkey patch。css

若是你沒有據說過duck punch,但你或許據說過duck typing。舉一個通俗的例子,如何辨別一隻鴨子:html

When I see a bird that walks like a duck and swims like a duck and quacks like a duck, I call that bird a duck.java

沒錯,若是我發現有一類動物像鴨子同樣叫,像鴨子同樣游泳,那麼它就是一隻鴨子!jquery

這個檢測看上去彷佛有一些理所固然和無厘頭,但卻很是的實用。 而且在編程中能夠用來解決一類問題——對於Javascript或者相似的動態語言,如何實現「接口」或者「基類」呢?咱們能夠徹底不用在意它們的過去如何,咱們只關係在使用它們的時候,方法的類型或者參數是不是咱們須要的:git

var quack = someObject.quack;

if (typeof quack == "function" && quck.length == arguLength)
{
         // This thing can quack
}

扯遠了,其實我想表達的是duck punch實際上是由duck typing演化而來的:github

if it walks like a duck and talks like a duck, it’s a duck, right? So if this duck is not giving you the noise that you want, you’ve got to just punch that duck until it returns what you expect.編程

當你想一隻鴨子發出驢的叫聲怎麼辦,揍到它發出驢的叫聲爲止……話說這讓我想到一個很是形象的笑話:架構

爲了測試美國、香港、中國大陸三地警察的實力, 聯合國將三隻兔子放在三個森林中,看三地警察誰先找出兔子。任務:找出兔子。
(中間省略......)
最後是某國警察,只有四個,先打了一天麻將,黃昏時一人拿一警棍進入森林,沒五分鐘,聽到森林裏傳來一陣動物的慘叫,某國警察一人抽着一根菸有說有笑的出來,後面拖着一隻鼻青臉腫的熊,熊奄奄一息的說到:「不要再打了,我就是兔子……」app

雖然duck punch有些暴力,但不失爲一個有效的方法。落實到代碼上來講就是讓原有的代碼兼容咱們須要的功能。好比Paul Irish博客上的這個例子:

/**
咱們都知道jQuery的`$.css`方法能夠經過使用顏色的名稱給元素進行顏色賦值。
但jQuery內置的顏色並不是是那麼豐富,若是咱們想添加咱們自定義的顏色名稱應該怎麼辦?好比咱們想添加`Burnt Sienna`這個顏色
*/

(function($){

    // 把原方法暫存起來:
    var _oldcss = $.fn.css;

    // 重寫原方法:
    $.fn.css = function(prop,value){

        // 把自定義的顏色寫進分支判斷裏,特殊狀況特殊處理
        if (/^background-?color$/i.test(prop) &&value.toLowerCase() === 'burnt sienna') {
            return _oldcss.call(this,prop,'#EA7E5D');

    // 通常狀況通常處理,調用原方法
        } else {
            return _oldcss.apply(this,arguments);
        }
    };
})(jQuery);

// 使用方法:
jQuery(document.body).css('backgroundColor','burnt sienna')

 

同時能夠推倒出`duck punch`的模式不過如此:

 

(function($){

    var _old = $.fn.method;

    $.fn.method = function(arg1,arg2){

        if ( ... condition ... ) {
            return ....
        } else { // do the default
            return _old.apply(this,arguments);
        }
    };
})(jQuery);        

 

可是這麼作有一個問題:須要修改原方法。這違背了「開放-封閉」原則,本應對拓展開放,對修改關閉。怎麼解決這個問題呢?使用AOP編程。

AOP

入門

AOP全稱爲`Aspect-oriented programming`,很明顯這是相對於`Object-oriented programming`而言。`Aspect`能夠翻譯爲「切面」或者「側面」,因此AOP也就是面向切面編程。

怎麼理解切面?

在面向對象編程中,咱們定義的類一般是領域模型,它的擁有的方法一般是和純粹的業務邏輯相關。好比:

Class Person
{
    private int money;
    public void pay(int price)
    {
        this.money = this.money - price; 
    }
}    

 

但一般實際狀況會更復雜,好比咱們須要在付款的`pay`方法中加入受權檢測,或者用於統計的日誌發送,甚至容錯代碼。因而代碼會變成這樣:

Class Person
{
    private int money
    public void pay(price)
    {
        try 
        {
              if (checkAuthorize() == true) {
                      this.money = this.money - price; 
                      sendLog();
              }
        }
        catch (Exception e)
        {

        } 
     }
}        

 

更可怕的是,其餘的方法中也要添加類似的代碼,這樣以來代碼的可維護性和可讀性便成了很大的問題。咱們但願把這些零散可是公共的非業務代碼收集起來,更友好的使用和管理他們,這即是切面編程。切面編程在避免修改遠代碼的基礎上實現了代碼的複用。就比如把不一樣的對象橫向剖開,關注於內部方法改造。而面向對象編程更關注的是總體的架構設計。


實現


在上一節中介紹的duck punch與切面編程相似,都是在改造原方法的同時保證原方法功能。但就像結尾說的同樣,直接修改原方法的模式有悖於面向對象最佳實踐的原則。

Javascript能夠採用裝飾者模式(給原對象添加額外的職責但避免修改原對象)實現AOP編程。注意在這裏強調的是**實現**,我進一步想強調的是,切面編程只是一種思想,而裝飾者模式只是實踐這種思想的一種手段而已,好比在Java中又能夠採用代理模式等。切面編程在Java中發揮的餘地更多,也更標準,本想把Java的實現模式也搬來這篇文章中,但不才Java水平有限,對Java的實現不是很是理解。在這裏就只展現Javascript的實現。

AOP中有一些概念須要介紹一下,雖然咱們不必定要嚴格執行

- joint-point:原業務方法;
- advice:攔截方式
- point-cut:攔截方法

關於這三個概念咱們能夠串起來能夠這麼理解:

當咱們使用AOP改造一個原業務方法(joint-point)時,好比加入日誌發送功能(point-cut),咱們要考慮在什麼狀況下(advice)發送日誌,是在業務方法觸發以前仍是以後;仍是在拋出異常的時候,仍是由日誌發送是否成功再決定是否執行業務方法。

好比gihub上的meld這個開源項目,就是一個很典型的AOP類庫,咱們看看它的API:

 

// 假設咱們有一個對象myObject, 而且該對象有一個doSomething方法:

var myObject = {
        doSomething: function(a, b) {
                return a + b;
        }
};

// 如今咱們想拓展它,在執行那個方法以後打印出剛剛執行的結果:

var remover = meld.after(myObject, 'doSomething', function(result) {
        console.log('myObject.doSomething returned: ' + result);
});

// 試試執行看:

myObject.doSomething(1, 2); // Logs: "myObject.doSomething returned: 3"

// 這個時候咱們想移除剛剛的修改:

remover.remove();

由此能夠看出,AOP接口一般須要三個參數,被修改的對象,被修改對象的方法(joint-point),以及觸發的時機(adivce),還有觸發的動做(point-cut)。上面說了那麼多的概念,如今可能要讓各位失望了,Javascript的實現原理其實很是簡單

function doAfter(target, method, afterFunc){
        var func = target[method];
        return function(){
                var res = func.apply(this, arguments);
                afterFunc.apply(this, arguments);
                return res; 
        };
}

固然,若是想看到更完備的解決方案和代碼能夠參考上面所說的meld項目

結束語

這一篇必定讓你失望了,代碼簡單又寥寥無幾。本篇主要在於介紹有關duck和AOP的這幾類思想,我想編程的樂趣不只僅在於落實在編碼上,更在於整個架構的設計。提升代碼的可維護性和可拓展性會比高深莫測的代碼更重要。

 

參考文獻:

- How to Fulfill Your Own Feature Request -or- Duck Punching With jQuery!
- Duck Punching JavaScript - Metaprogramming with Prototype
- Does JavaScript have the interface type (such as Java's 'interface')
- AOP技術基礎

相關文章
相關標籤/搜索