首頁  >  文章  >  web前端  >  聊Javascript中的AOP編程

聊Javascript中的AOP編程

黄舟
黄舟原創
2017-02-25 13:39:061274瀏覽


 Duck punch

  我們先不談AOP編程,先從聊Javascript中的AOP編程 punch編程談起。

  如果你去wikipedia中找聊Javascript中的AOP編程 punch,你查閱到的應該是monkey patch這個詞條。根據解釋,Monkey patch這個詞來自guerrilla patch,意為在運行中悄悄的改變代碼,而guerrilla 這個詞與gorilla 同音,而後者意又與monkey相近(前者為“猩猩”的意思),最後就演變為了monkey patch。

  如果你沒聽過聊Javascript中的AOP編程 punch,但你或許聽過聊Javascript中的AOP編程 typing。舉一個通俗的例子,如何辨別一隻鴨子:

When I see a bird that walks like a 聊Javascript中的AOP編程 and swims like a 聊Javascript中的AOP編程 and quacks like a 聊Javascript中的AOP編程, I call that bird a 聊Javascript中的AOP編程.

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

聊Javascript中的AOP編程

  這個檢測看起來似乎有些理所當然和無厘頭,但卻非常的實用。 並且在程式設計中可以用來解決一類問題──對於Javascript或類似的動態語言,如何實作「介面」或「基底類別」呢?我們可以完全不用在乎它們的過去如何,我們只關係在使用它們的時候,方法的類型或者參數是否是我們需要的:

var quack = someObject.quack;

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

  扯遠了,其實我想表達的是聊Javascript中的AOP編程 punch其實是由聊Javascript中的AOP編程 typing演化而來的:

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

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

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

  雖然聊Javascript中的AOP編程 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')

  同時可以推倒出聊Javascript中的AOP編程 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)
        {

        }   
    }
}

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

  實作

  在上一節介紹的聊Javascript中的AOP編程 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),以及触发的时机(apce),还有触发的动作(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项目

 结束语

  这一篇一定让你失望了,代码简单又寥寥无几。本篇主要在于介绍有关聊Javascript中的AOP編程和AOP的这几类思想,我想编程的乐趣不仅仅在于落实在编码上,更在于整个架构的设计。提高代码的可维护性和可拓展性会比高深莫测的代码更重要。

 以上就是聊Javascript中的AOP编程的内容,更多相关内容请关注PHP中文网(www.php.cn)!


陳述:
本文內容由網友自願投稿,版權歸原作者所有。本站不承擔相應的法律責任。如發現涉嫌抄襲或侵權的內容,請聯絡admin@php.cn