Duck punch
Let’s not talk about AOP programming first, let’s start with duck punch programming.
If you search for duck punch in Wikipedia, the entry you should find is monkey patch. According to the explanation, the word monkey patch comes from guerrilla patch, which means quietly changing the code during operation. The word guerrilla has the same pronunciation as gorilla, and the latter means similar to monkey (the former means "gorilla"), and finally evolved into For monkey patch.
If you haven’t heard of duck punch, you may have heard of duck typing. To give a popular example, how to identify a duck:
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.
That's right, if I find There is an animal that quacks like a duck and swims like a duck, then it is a duck!
This test may seem a little natural and nonsensical, but it is very practical. And it can be used to solve a type of problem in programming - for Javascript or similar dynamic languages, how to implement "interface" or "base class"? We don’t need to care about their past at all. We only care about whether the method type or parameters are what we need when using them:
var quack = someObject.quack; if (typeof quack == "function" && quck.length == arguLength) { // This thing can quack }
I’m going a step further. In fact, what I want to say is that duck punch is actually derived from duck typing. Evolved from:
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.
What should you do if you want a duck to bray like a donkey? Beat it until it brays like a donkey... This reminds me of a very vivid joke:
To test the United States and Hong Kong , the strength of the police in the three places in mainland China, the United Nations placed three rabbits in three forests to see who of the three police officers can find the rabbit first. Task: Find the rabbit. (The middle is omitted...) Finally, there were only four policemen from a certain country. They played mahjong for a day. At dusk, each of them took a baton and entered the forest. Within five minutes, they heard the screams of animals coming from the forest. He came out talking and laughing while smoking a cigarette, and behind him was a bear with a bruised nose and swollen face. The bear was dying and said: "Stop hitting me, I'm just a rabbit..."
Although the duck punch is a bit violent, it is still an effective one. Methods. When it comes to code implementation, it means making the original code compatible with the functions we need. For example, this example on Paul Irish's blog:
/** 我们都知道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')
At the same time, the duck punch mode can be reversed, but it is just like this:
(function($){ var _old = $.fn.method; $.fn.method = function(arg1,arg2){ if ( ... condition ... ) { return .... } else { // do the default return _old.apply(this,arguments); } }; })(jQuery);
But there is a problem with this: the original method needs to be modified. This violates the "open-closed" principle, which should be open for expansion and closed for modification. How to solve this problem? Use AOP programming.
AOP
Getting Started
AOP stands for Aspect-oriented programming, which is obviously relative to Object-oriented programming. Aspect can be translated as "aspect" or "side", so AOP is aspect-oriented programming.
How to understand slices?
In object-oriented programming, the classes we define are usually domain models, and the methods they have are usually related to pure business logic. For example:
Class Person { private int money; public void pay(int price) { this.money = this.money - price; } }
But usually the actual situation is more complicated. For example, we need to add authorization detection to the payment method, or send logs for statistics, or even fault-tolerant code. So the code will become like this:
Class Person { private int money public void pay(price) { try { if (checkAuthorize() == true) { this.money = this.money - price; sendLog(); } } catch (Exception e) { } } }
What’s even more frightening is that similar code must be added to other methods, so the maintainability and readability of the code become a big problem. We hope to collect these scattered but common non-business codes and use and manage them more friendly. This is aspect programming. Aspect programming achieves code reuse on the basis of avoiding modification of remote code. It's like cutting different objects horizontally and focusing on the transformation of internal methods. Object-oriented programming pays more attention to the overall architectural design.
realization
在上一节中介绍的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技术基础