聊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技术基础

相关文章
相关标签/搜索