聊聊Javascript中的AOP编程

Duck punch

  我们先不谈AOP编程,先从duck punch编程谈起。

  如果你去wikipedia中查找duck punch,你查阅到的应该是monkey patch这个词条。根据解释,Monkey patch这个词来源于 guerrilla patch,意为在运行中悄悄的改变代码,而 guerrilla 这个词与 gorilla 同音,而后者意又与monkey相近(前者为“猩猩”的意思),最后就演变为了monkey patch。

  如果你没有听说过duck punch,但你或许听说过duck typing。举一个通俗的例子,如何辨别一只鸭子:

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.

  没错,如果我发现有一类动物像鸭子一样叫,像鸭子一样游泳,那么它就是一只鸭子!

  这个检测看上去似乎有一些理所当然和无厘头,但却非常的实用。 并且在编程中可以用来解决一类问题——对于Javascript或者类似的动态语言,如何实现“接口”或者“基类”呢?我们可以完全不用在乎它们的过去如何,我们只关系在使用它们的时候,方法的类型或者参数是否是我们需要的:


1

2

3

4

5

6

var quack = someObject.quack;

if (typeof quack == "function" && quck.length == arguLength)

{

    // This thing can quack

}

  扯远了,其实我想表达的是duck punch其实是由duck typing演化而来的:

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.

  当你想一只鸭子发出驴的叫声怎么办,揍到它发出驴的叫声为止……话说这让我想到一个非常形象的笑话:

为了测试美国、香港、中国大陆三地警察的实力, 联合国将三只兔子放在三个森林中,看三地警察谁先找出兔子。任务:找出兔子。 (中间省略……) 最后是某国警察,只有四个,先打了一天麻将,黄昏时一人拿一警棍进入森林,没五分钟,听到森林里传来一阵动物的惨叫,某国警察一人抽着一根烟有说有笑的出来,后面拖着一只鼻青脸肿的熊,熊奄奄一息的说到:“不要再打了,我就是兔子……”

  虽然duck punch有些暴力,但不失为一个有效的方法。落实到代码上来说就是让原有的代码兼容我们需要的功能。比如Paul Irish博客上的这个例子:


1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

20

21

22

23

24

25

26

/**

    我们都知道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的模式不过如此:


1

2

3

4

5

6

7

8

9

10

11

12

13

(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也就是面向切面编程。

  怎么理解切面?

  在面向对象编程中,我们定义的类通常是领域模型,它的拥有的方法通常是和纯粹的业务逻辑相关。比如:


1

2

3

4

5

6

7

8

Class Person

{

    private int money;

    public void pay(int price)

    {

         this.money = this.money - price;  

    }

}

  但通常实际情况会更复杂,比如我们需要在付款的pay方法中加入授权检测,或者用于统计的日志发送,甚至容错代码。于是代码会变成这样:


1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

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:


1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

20

21

// 假设我们有一个对象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的实现原理其实非常简单


1

2

3

4

5

6

7

8

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的这几类思想,我想编程的乐趣不仅仅在于落实在编码上,更在于整个架构的设计。提高代码的可维护性和可拓展性会比高深莫测的代码更重要。

  其实上面

 参考文献:

时间: 2024-10-25 03:14:33

聊聊Javascript中的AOP编程的相关文章

聊Javascript中的AOP编程

我们先不谈AOP编程,先从duck punch编程谈起. 如果你去wikipedia中查找duck punch,你查阅到的应该是monkey patch这个词条.根据解释,Monkey patch这个词来源于 guerrilla patch,意为在运行中悄悄的改变代码,而 guerrilla这个词与 gorilla 同音,而后者意又与monkey相近(前者为“猩猩”的意思),最后就演变为了monkey patch. 如果你没有听说过duck punch,但你或许听说过duck typing.举一

JS函数式编程【译】4.在Javascript中实现函数式编程的技术

?? Functional Programming in Javascript 主目录上一章 建立函数式编程环境 第四章 在Javascript中实现函数式编程的技术 扶好你的帽子,我们现在要真正进入函数式的思想了. 这章我们继续下面的内容: 把所有的核心概念放到一个集中的范式里 探索函数式编程之美 一步步跟踪函数式模式相互交织的逻辑 我们将贯穿整章建立一个简单的应用做一些很酷的事情 你可能已经注意到,在上一章我们介绍Javascript的函数式库的时候引入了一些概念, 而不是在第二章<函数式编

直播开始:&#39;云榨汁机&#39;诞生记--聊聊JavaScript中的&#39;业务建模&#39;

闭包是JavaScript中的一个重要特性,在之前的博文中,我们说闭包是一个'看似简单,其实很有内涵'的特性.当我们用JavaScript来实现相对复杂的业务建模时,我们可以如何利用'闭包'这个特性呢?JavaScript中的'原型继承',又可以解决业务建模中的哪些问题呢?今天我们就通过一家'榨汁机工厂'生产设计'榨汁机'的故事,来聊一聊'闭包'和'原型继承'在业务建模中的作用.现在直播开始: 1> 工厂默认选用A型刀头方案制造榨汁机 例子当中我们主要涉及到2个函数:1.榨汁机的生产工厂(Jui

Javascript 中的函数式编程

本文和大家分享的主要是javascript中函数式编程相关内容,一起来看看吧,希望对大家学习javascript有所帮助. 函数式编程(functional programming)或称函数程序设计,又称泛函编程,是一种编程范型,比起命令式编程,函数式编程更加强调程序执行的结果而非执行的过程,倡导利用若干简单的执行单元让计算结果不断渐进,逐层推导复杂的运算,而不是设计一个复杂的执行过程. 函数式编程,近年来一直被炒得火热,国内外的开发者好像都在议论和提倡这种编程范式.在众多的函数式语言中,Jav

深入理解javascript中实现面向对象编程方法

介绍Javascript中面向对象编程思想之前,需要对以下几个概念有了解: 1. 浅拷贝和深拷贝:程序在运行过程中使用的变量有在栈上的变量和在堆上的变量,在对象或者变量的赋值操作过程中,大多数情况先是复制栈上的信息,这样就会出现以下情况,如果变量是对象,那么这一操作,复制的只是真正对象所在 的堆内存空间的起始地址,这就是所谓的浅拷贝,如果是深拷贝,则是在内存堆空间中重新分配一个内存,并把分配的内存的起始地址复制过去. 2. 引用类型数据和值类型数据:谈到引用类型数据和值类型数据,自然而然的联想到

Java——面向切面编程,Spring中的AOP编程

面向切面编程 AOP思想:将横向重复代码,纵向抽取出来 AOP体现--Filter AOP体现--拦截器 AOP体现--动态代理 Spring中实现AOP思想 原理:Spring可以为容器中管理的对象生成代理对象 代理分为动态代理和cglib代理: 动态代理(优先) 被代理对象必须要实现接口,才能产生代理对象,如果没有接口将不能使用动态代理技术,换句话说,就是代理对象和被代理要实现同一接口 cglib代理 第三方代理技术,cglib代理,可以对任何类生成代理,代理的原理是对目标对象进行继承代理,

JavaScript中数组高级编程实践

今天我们来全面介绍 JavaScript 中 数组的高级使用,与EcmaScript5 Array API 实战. 利用这些新的API 和 技巧,将提高你的开发效率 和 代码的水平. 理解这些原生的API是 非常有必要的,假以时日,我们也可以写出 underscore ...这样的工具库来. Come on Baby! 先看一下 Array.prototype 的全家福. 在JavaScript 中,数组就是有顺序的存储一系列值,长度动态扩容. ,先看我们的EcmaScript 规范中的  对

在JavaScript中实现异步编程模式的方法

本文总结了”异步模式”编程的4种方法,理解它们可以让你写出结构更合理.性能更出色.维护更方便的Javascript程序. 一.回调函数 这是异步编程最基本的方法. 假定有两个函数f1和f2,后者等待前者的执行结果. f1(); f2(); 如果f1是一个很耗时的任务,可以考虑改写f1,把f2写成f1的回调函数. function f1(callback){ setTimeout(function () { // f1的任务代码 callback(); }, 1000); } 执行代码就变成下面这

聊聊javascript中的面向对象

面向对象这个东西一直晕晕乎乎的,正好这段时间没有活,可以好好整理整理了! 1.什么是对象? 其实这个说起来一切东西都是对象 2.目前我们使用对象的时候,使用的是两种设计模式杂糅起来的 分别是原型模式和构造模式: 原型模式 需要了解的就是原型是什么? 原型:摘录自<javascript高级程序设计> 我自己的理解就是:本来属性 方法都是在构造函数中,但是现在用函数名.prototype{}里面放入属性和方法:这个就是原型对象 之后我们整个过程其实就是创建的实例和原型对象之间的关系,而不是原型对象