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或者类似的动态语言,如何实现“接口”或者“基类”呢?我们可以完全不用在乎它们的过去如何,我们只关系在使用它们的时候,方法的类型或者参数是否是我们需要的:

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博客上的这个例子:

/**
我们都知道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技术基础

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

  1. 聊聊Javascript中的AOP编程

    Duck punch 我们先不谈AOP编程,先从duck punch编程谈起. 如果你去wikipedia中查找duck punch,你查阅到的应该是monkey patch这个词条.根据解释,Mon ...

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

    介绍Javascript中面向对象编程思想之前,需要对以下几个概念有了解: 1. 浅拷贝和深拷贝:程序在运行过程中使用的变量有在栈上的变量和在堆上的变量,在对象或者变量的赋值操作过程中,大多数情况先是 ...

  3. 菜鸟聊JavaScript中this

    菜鸟聊this this在JavaScript中是一个比较头疼的问题,我现在以一枚菜鸟的观点结合代码简单的谈下JavaScript中的this指向问题. 1.例子1 function a() { va ...

  4. .NET Core中实现AOP编程

    AOP全称Aspect Oriented Progarmming(面向切面编程),其实AOP对ASP.NET程序员来说一点都不神秘,你也许早就通过Filter来完成一些通用的功能,例如你使用Autho ...

  5. 通过代数,数字,欧几里得平面和分形讨论JavaScript中的函数式编程

    本文是对函数式编程范式的系列文章从而拉开了与以下延续一个. 介绍 在JavaScript中,函数只是对象.因此,可以构造函数,作为参数传递,从函数返回或分配给变量.因此,JavaScript具有一流的 ...

  6. (转).NET Core中实现AOP编程

    原文地址:https://www.cnblogs.com/xiandnc/p/10088159.html AOP全称Aspect Oriented Progarmming(面向切面编程),其实AOP对 ...

  7. javaScript中的异步编程模式

    1.事件模型 let button = document.getElementById("my-btn"); button.onclick = function(event) { ...

  8. [置顶] Ruby,Scala和JavaScript中的函数式编程(一)

    函数式编程(英语:Functional programming)或者函数程序设计,又称泛函编程,是一种编程范型,它将电脑运算视为数学上的函数计算,并且避免使用程序状态以及易变对象.函数编程语言最重要的 ...

  9. .NET下集中实现AOP编程的框架

    一.Castle 使用这个框架呢,首先是需要安装NuGet包. 先建立一个控制台项目,然后在NuGet中搜索Castle.Windsor,不出意外的话应该能找到如下的包 然后安装,会自动的安装包Cas ...

随机推荐

  1. Django如何搭建服务器

    建立django站点: 新建Django项目,我这里是使用命令创建项目的. django-admin startproject HelloDjango  创建HelloDjango项目 然后进入到He ...

  2. 搭建coreseek(sphinx+mmseg3)详细安装配置+php之sphinx扩展安装+php调用示例

    http://blog.csdn.net/e421083458/article/details/21529969 常用的命令 ps -ef|grep searchd 如果你开了search服务后,你命 ...

  3. Percona Server 5.6.13-61.0 首个 GA 版本发布

    Percona Server 5.6 的首个 GA 版本发布了,版本号是 5.6.13-61.0 ,该版本基于 MySQL 5.6.13 改进内容包括: New Features: Percona S ...

  4. Intellij IDEA工具Java web 环境搭建

    Java web 环境搭建 环境依赖 操作系统:Windows 7 64位 开发工具:IntelliJ IDEA 13.1.4 开发工具依赖环境 JDK版本:1.7+ 开发工具依赖插件 包管理:Mav ...

  5. Linux 学习笔记(一) 入门

    Shell 显示Shell类型 $ps 切换Shell $[Shell 名称]  ex. $tcsh 快捷键 Ctrl + Z:挂起,可用jobs查看到,fg恢复运行 Ctrl + W:删除单词 Ct ...

  6. 如何做到在虚拟数据库和真实数据库之间自由切换?【低调赠送:QQ高仿版GG 4.4 最新源码】

    记得以前在公司上班时,有时候白天的活没干完,我就会把工作带回家晚上加班继续做.但是,我们开发用的数据库是部署在公司局网内部的一台服务器上的,在家里是肯定连不上这台机器的.在家里没有数据库,服务端就跑不 ...

  7. Linux4:useradd、userdel、passwd、groupadd、chgrp、chown、df、du、sort、wget

    useradd 添加新的用户账号,只有root账户可以操作 -d 目录:指定用户主目录(默认在home下),若此目录不存在可同时使用-m创建主目录 -g 用户组:指定用户所属的用户组 -G 用户组:指 ...

  8. 作业2源程序版本管理软件优缺点,及Github注册

    目前流行的源程序管理软件和项目管理软件主要有以下一些: 1.Visual Source Safe 优点:如果开发工具是VS.NET,用VSS较合适,方便,安装配置和使用都简单,版本控制简单,打labe ...

  9. DDD领域驱动设计之聚合、实体、值对象

    关于具体需求,请看前面的博文:DDD领域驱动设计实践篇之如何提取模型,下面是具体的实体.聚合.值对象的代码,不想多说什么是实体.聚合等概念,相信理论的东西大家已经知晓了.本人对DDD表示好奇,没有在真 ...

  10. js模版引擎handlebars.js实用教程——with-终极this应用

    返回目录 <!DOCTYPE html> <html> <head> <META http-equiv=Content-Type content=" ...