聊Javascript中的AOP編程
我們先不談AOP編程,先從duck punch編程談起。
如果你去wikipedia中查找duck punch,你查閱到的應(yīng)該是monkey patch這個(gè)詞條。根據(jù)解釋,Monkey patch這個(gè)詞來源于 guerrilla patch,意為在運(yùn)行中悄悄的改變代碼,而 guerrilla 這個(gè)詞與 gorilla 同音,而后者意又與monkey相近(前者為“猩猩”的意思),最后就演變?yōu)榱薽onkey patch。
如果你沒有聽說過duck punch,但你或許聽說過duck typing。舉一個(gè)通俗的例子,如何辨別一只鴨子:
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.
沒錯(cuò),如果我發(fā)現(xiàn)有一類動(dòng)物像鴨子一樣叫,像鴨子一樣游泳,那么它就是一只鴨子!
這個(gè)檢測(cè)看上去似乎有一些理所當(dāng)然和無厘頭,但卻非常的實(shí)用。 并且在編程中可以用來解決一類問題——對(duì)于Javascript或者類似的動(dòng)態(tài)語言,如何實(shí)現(xiàn)“接口”或者“基類”呢?我們可以完全不用在乎它們的過去如何,我們只關(guān)系在使用它們的時(shí)候,方法的類型或者參數(shù)是否是我們需要的:
var quack = someObject.quack;if (typeof quack == "function" && quck.length == arguLength){ // This thing can quack}
扯遠(yuǎn)了,其實(shí)我想表達(dá)的是duck punch其實(shí)是由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.
當(dāng)你想一只鴨子發(fā)出驢的叫聲怎么辦,揍到它發(fā)出驢的叫聲為止……話說這讓我想到一個(gè)非常形象的笑話:
為了測(cè)試美國(guó)、香港、中國(guó)大陸三地警察的實(shí)力, 聯(lián)合國(guó)將三只兔子放在三個(gè)森林中,看三地警察誰先找出兔子。任務(wù):找出兔子。 (中間省略……) 最后是某國(guó)警察,只有四個(gè),先打了一天麻將,黃昏時(shí)一人拿一警棍進(jìn)入森林,沒五分鐘,聽到森林里傳來一陣動(dòng)物的慘叫,某國(guó)警察一人抽著一根煙有說有笑的出來,后面拖著一只鼻青臉腫的熊,熊奄奄一息的說到:“不要再打了,我就是兔子……”
雖然duck punch有些暴力,但不失為一個(gè)有效的方法。落實(shí)到代碼上來說就是讓原有的代碼兼容我們需要的功能。比如Paul Irish博客上的這個(gè)例子:
/** 我們都知道jQuery的`$.css`方法可以通過使用顏色的名稱給元素進(jìn)行顏色賦值。 但jQuery內(nèi)置的顏色并非是那么豐富,如果我們想添加我們自定義的顏色名稱應(yīng)該怎么辦?比如我們想添加`Burnt Sienna`這個(gè)顏色*/(function($){// 把原方法暫存起來: var _oldcss = $.fn.css; // 重寫原方法: $.fn.css = function(prop,value){// 把自定義的顏色寫進(jìn)分支判斷里,特殊情況特殊處理if (/^background-?color$/i.test(prop) && value.toLowerCase() === 'burnt sienna') { return _oldcss.call(this,prop,'#EA7E5D');// 一般情況一般處理,調(diào)用原方法} else { return _oldcss.apply(this,arguments);} };})(jQuery);// 使用方法:jQuery(document.body).css('backgroundColor','burnt sienna')
同時(shí)可以推倒出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);
但是這么做有一個(gè)問題:需要修改原方法。這違背了“開放-封閉”原則,本應(yīng)對(duì)拓展開放,對(duì)修改關(guān)閉。怎么解決這個(gè)問題呢?使用AOP編程。
AOP入門AOP全稱為Aspect-oriented programming,很明顯這是相對(duì)于Object-oriented programming而言。Aspect可以翻譯為“切面”或者“側(cè)面”,所以AOP也就是面向切面編程。
怎么理解切面?
在面向?qū)ο缶幊讨校覀兌x的類通常是領(lǐng)域模型,它的擁有的方法通常是和純粹的業(yè)務(wù)邏輯相關(guān)。比如:
Class Person{ private int money; public void pay(int price) { this.money = this.money - price; }}
但通常實(shí)際情況會(huì)更復(fù)雜,比如我們需要在付款的pay方法中加入授權(quán)檢測(cè),或者用于統(tǒng)計(jì)的日志發(fā)送,甚至容錯(cuò)代碼。于是代碼會(huì)變成這樣:
Class Person{ private int money public void pay(price) {try { if (checkAuthorize() == true) {this.money = this.money - price; sendLog(); }}catch (Exception e){} }}
更可怕的是,其他的方法中也要添加相似的代碼,這樣以來代碼的可維護(hù)性和可讀性便成了很大的問題。我們希望把這些零散但是公共的非業(yè)務(wù)代碼收集起來,更友好的使用和管理他們,這便是切面編程。切面編程在避免修改遠(yuǎn)代碼的基礎(chǔ)上實(shí)現(xiàn)了代碼的復(fù)用。就好比把不同的對(duì)象橫向剖開,關(guān)注于內(nèi)部方法改造。而面向?qū)ο缶幊谈P(guān)注的是整體的架構(gòu)設(shè)計(jì)。
實(shí)現(xiàn)在上一節(jié)中介紹的duck punch與切面編程類似,都是在改造原方法的同時(shí)保證原方法功能。但就像結(jié)尾說的一樣,直接修改原方法的模式有悖于面向?qū)ο笞罴褜?shí)踐的原則。
Javascript可以采用裝飾者模式(給原對(duì)象添加額外的職責(zé)但避免修改原對(duì)象)實(shí)現(xiàn)AOP編程。注意在這里強(qiáng)調(diào)的是實(shí)現(xiàn),我進(jìn)一步想強(qiáng)調(diào)的是,切面編程只是一種思想,而裝飾者模式只是實(shí)踐這種思想的一種手段而已,比如在Java中又可以采用代理模式等。切面編程在Java中發(fā)揮的余地更多,也更標(biāo)準(zhǔn),本想把Java的實(shí)現(xiàn)模式也搬來這篇文章中,但不才Java水平有限,對(duì)Java的實(shí)現(xiàn)不是非常理解。在這里就只展示Javascript的實(shí)現(xiàn)。
AOP中有一些概念需要介紹一下,雖然我們不一定要嚴(yán)格執(zhí)行
joint-point:原業(yè)務(wù)方法;advice:攔截方式point-cut:攔截方法關(guān)于這三個(gè)概念我們可以串起來可以這么理解:
當(dāng)我們使用AOP改造一個(gè)原業(yè)務(wù)方法(joint-point)時(shí),比如加入日志發(fā)送功能(point-cut),我們要考慮在什么情況下(advice)發(fā)送日志,是在業(yè)務(wù)方法觸發(fā)之前還是之后;還是在拋出異常的時(shí)候,還是由日志發(fā)送是否成功再?zèng)Q定是否執(zhí)行業(yè)務(wù)方法。
比如gihub上的meld這個(gè)開源項(xiàng)目,就是一個(gè)很典型的AOP類庫(kù),我們看看它的API:
// 假設(shè)我們有一個(gè)對(duì)象myObject, 并且該對(duì)象有一個(gè)doSomething方法:var myObject = { doSomething: function(a, b) {return a + b; }};// 現(xiàn)在我們想拓展它,在執(zhí)行那個(gè)方法之后打印出剛剛執(zhí)行的結(jié)果:var remover = meld.after(myObject, 'doSomething', function(result) { console.log('myObject.doSomething returned: ' + result);});// 試試執(zhí)行看:myObject.doSomething(1, 2); // Logs: "myObject.doSomething returned: 3"http:// 這個(gè)時(shí)候我們想移除剛剛的修改:remover.remove();
由此可以看出,AOP接口通常需要三個(gè)參數(shù),被修改的對(duì)象,被修改對(duì)象的方法(joint-point),以及觸發(fā)的時(shí)機(jī)(adivce),還有觸發(fā)的動(dòng)作(point-cut)。上面說了那么多的概念,現(xiàn)在可能要讓各位失望了,Javascript的實(shí)現(xiàn)原理其實(shí)非常簡(jiǎn)單
function doAfter(target, method, afterFunc){ var func = target[method]; return function(){var res = func.apply(this, arguments);afterFunc.apply(this, arguments);return res; };}
當(dāng)然,如果想看到更完備的解決方案和代碼可以參考上面所說的meld項(xiàng)目
結(jié)束語這一篇一定讓你失望了,代碼簡(jiǎn)單又寥寥無幾。本篇主要在于介紹有關(guān)duck和AOP的這幾類思想,我想編程的樂趣不僅僅在于落實(shí)在編碼上,更在于整個(gè)架構(gòu)的設(shè)計(jì)。提高代碼的可維護(hù)性和可拓展性會(huì)比高深莫測(cè)的代碼更重要。
其實(shí)上面
參考文獻(xiàn):How to Fulfill Your Own Feature Request -or- Duck Punching With jQuery!Duck Punching JavaScript - Metaprogramming with PrototypeDoes JavaScript have the interface type (such as Java’s ‘interface’)?AOP技術(shù)基礎(chǔ)相關(guān)文章:
1. 以PHP代碼為實(shí)例詳解RabbitMQ消息隊(duì)列中間件的6種模式2. html小技巧之td,div標(biāo)簽里內(nèi)容不換行3. PHP字符串前后字符或空格刪除方法介紹4. 將properties文件的配置設(shè)置為整個(gè)Web應(yīng)用的全局變量實(shí)現(xiàn)方法5. nestjs實(shí)現(xiàn)圖形校驗(yàn)和單點(diǎn)登錄的示例代碼6. AspNetCore&MassTransit Courier實(shí)現(xiàn)分布式事務(wù)的詳細(xì)過程7. XML入門的常見問題(一)8. jsp cookie+session實(shí)現(xiàn)簡(jiǎn)易自動(dòng)登錄9. css進(jìn)階學(xué)習(xí) 選擇符10. Echarts通過dataset數(shù)據(jù)集實(shí)現(xiàn)創(chuàng)建單軸散點(diǎn)圖
