第三課 常用范式(3)
3.3 切面范式——多角度看問題
橫看成嶺側成峰 ——《蘇轼·題西林壁》
關鍵詞:編程范式,SoC,DRY,AOP,Aspect,join point,pointcut,advice,OOP
摘要:AOP簡談
?提問
什麼是SoC和DRY?
如何有效地避免紊亂、松散、重復的代碼?
抽象與分解的原則是什麼?
什麼是橫切關注點?
接入點與切入點有何區別?
什麼是編織?有哪些不同的編織方法?
實施AOP有哪些步驟?
為什麼說AOP是OOP的一種補充?
為什麼提倡盡可能地閱讀原文的書籍和資料?
:講解
課間休息剛一結束,引號便重開話題:“OOP方興未艾,AOP又開始嶄露頭角。AOP算是OOP的一種補充、一種分支還是一種超越?”
歎號故作捶胸頓足狀:“OOP還沒有完全吃透,又來了個什麼AOP。”
“不同的人對新生事物采取不同的態度。”冒號王顧左右而言他,“追星族傾向於盲目追捧,唯恐落伍,他們信奉新潮的、流行的就是好的;守舊派傾向於本能抗拒,回避求新,他們認為經典的、傳統的才是好的。”
引號和歎號互視一眼,不情願地戴上了老冒派發的帽子。
冒號續道:“從宏觀角度看,太陽底下沒有新鮮事——AOP無非是SoC原理和DRY原則的一種應用;從微觀角度看,太陽每天都是新的——AOP雖自OOP的土壤中長出,卻脫離藩籬自成一體,並且嫁接到非OOP的領地,不僅在純過程式語言、函數式語言、甚至邏輯式語言中得到發展,而且本身也具備了一定的聲明式語言特征,成為一種新的軟件模塊化方式。”
問號舉手:“什麼是SoC和DRY?”
引號代答:“SoC就是Separation of concerns,即關注點分離;DRY是Don’t Repeat Yourself,即盡量減少重復代碼。”
“答案正確,加十分!”冒號戲贊道,“不良代碼通常有兩種病征:一是結構混亂,或聚至糾纏打結、或散至七零八落;二是代碼重復,疊床架屋、臃腫不堪。治療此類病症一個有效的方法是抽象與分解:從問題中抽象出一些關注點,再以此為基礎進行分解。分解後的子問題主題鮮明且獨立完備,既不會牽一發而動全身,也不會四分五裂、支離破碎。同時具有相同特征的部分可以象代數中的公因子一樣提取出來,提高了重用性,減少了重復性。”
句號醒悟道:“這不就是模塊化嗎?”
“准確地說,抽象是前提,分解是方式,模塊化是結果。”冒號很講究精確,“大家記得庖丁解牛的故事吧?在常人眼中復雜的牛體,庖丁經過抽象,已目無全牛,及至提刀分解,自是游刃有余。待牛如土委地,模塊化既成。”
句號舉一反三:“前面提到的編程范式的基本思想大多不也如此?將程序分別抽象分解為過程、函數、斷言、對象和進程,就依次成為過程式、函數式、邏輯式、對象式和並發式。至於泛型式——”
句號講不下去了。
“泛型式雖未引入新類型的模塊,其核心也是抽象出算法後與數據分解。”冒號為其解圍,“以此類推,切面式的AOP將程序抽象分解為切面。”
問號提問:“抽象與分解的原則是什麼?”
冒號作了個V字:“兩條:單一化,正交化。每個模塊職責明確專一,模塊之間相互獨立,即高聚合低耦合(high cohesion & low coupling)[1]。此原則相當普適,是分析復雜事物的一種基本方法,在數學和物理中應用得尤為廣泛,如質因式分解、正交分解、譜分解等等。”
逗號調皮地抬槓:“為什麼稱為正交化呢?斜交化不行嗎?”
冒號呵呵一笑:“在數學中互為正交的兩個向量在彼此方向上投影為零,意味著彼此獨立、互不影響,斜交可不行。”
逗號吐了吐舌頭。
“誠如前述,AOP以切面為模塊。”冒號返回主題,“切面Aspect常直譯為‘方面’,但它描述的是橫切關注點(Cross-cutting concerns),故‘切面’更准確生動,而‘方面’則失之空泛呆板。何謂橫切關注點?顧名思義,乃是與程序的縱向主流執行方向橫向正交的關注焦點。不妨回顧一下,無論是過程式的函數,還是對象式的方法,都包含了完整的執行代碼。但有些代碼橫跨多個模塊,以片斷的形式散落在各處,雖具有相似的邏輯,卻無法用傳統的方式提煉成模塊,難以實現SoC與DRY。典型的例子如:在調用某些對象的方法、讀寫某些對象的域、拋出某些異常等等前後,需要用到統一的業務邏輯,諸如日志輸出、代碼跟蹤、性能監控、異常處理、安全檢查、事務管理等等。為解決此類問題,AOP應運而生。它將每類橫切關注點封裝到單獨的Aspect模塊中,將程序中的一些執行點與相應的代碼綁定起來。單個的執行點稱為接入點(join point),例如:調用某個對象的方法前後;符合預先指定條件的接入點集合稱為切入點(pointcut),例如:所有以set為命名開頭的方法;每段綁定的代碼稱為一個建議(advice)。”
問號有點疑問:“接入點與切入點有何區別?”
冒號釋疑:“望文生義,接入處是點,切入處是面,面由點組成。advice定義於切入點上,執行於接入點處。換言之,共享一段附加代碼的接入點組成了一個切入點。切入點一般用條件表達式來描述,不僅有廣泛性,還有預見性——以後新增的代碼如果含有滿足切入點條件的接入點,advice中的代碼便自動附著其上。這是AOP的威力所在,但有時也是麻煩所在。”
引號很較真:“好像一些書上把join point譯作連接點,把advice譯作通知。”
“誤導,完全是誤導!”冒號有些痛心疾首,“何謂join point?是advice中額外代碼接入之處,join顯為‘參加’、‘加入’之意。如果說翻作‘連接’ 只是因缺乏動感和方向性而不夠貼切的話,將advice譯作‘通知’則近乎荒謬了。advice是在原有程序流程中加入的額外流程,可理解為建議采取的措施,而‘通知’強調的是一種信息,難道是程序運行到join point的信息?抑或采取某種行動的信息?簡直不知所雲。”
頓了一會,冒號仍意猶未盡:“英文好的技術不好,技術好的英文不好,兩者都好的不屑去翻譯,導致市面上的譯書雖汗牛充棟,然佳作寥寥。這裡奉勸各位,如果真想成為優秀的程序員,一定要盡可能地讀原文的書籍、文章和文檔。事實上,凡是科學和藝術方面的專業人員,要想專業水平上一層台階,都應讀該專業權威經典的原文。要知道,語言之間的天塹原本難以彌合,譯者的專業水准、語言功底和嚴謹程度更是參差不齊。縱使萬事俱備,一年半載後的譯書便如隔夜的飯菜,雖剛出爐,已然不新鮮了。”
逗號抱怨:“英文雖讀得懂,但太慢、太費勁了。”
“多讀,讀多了就習慣了。”冒號鼓勵著,“對程序員來說,英語也是一門計算機語言,而且是必修的語言。”
課堂上有這麼一個規律,越是題外的話大家討論得越歡。下面果然開始嘈雜起來,冒號也樂得乘機歇歇嘴。
等大伙漸漸把視線重新聚焦到講台上,冒號才繼續講課:“從軟件重用的角度看,可以這麼理解AOP與OOP的關系:OOP只能沿著繼承樹的縱向方向重用,而AOP則彌補了OOP的不足,可以在橫向方向重用。這算是回答了引號開始提出的問題:AOP不是OOP的分支,也不能說是超越了OOP,而是OOP的一種補充——盡管AOP並不局限於OOP。”
問號的求知欲很強:“AOP實現的機理是什麼?”
冒號回答:“如果一個程序是一個管道系統,AOP就是在管道上鑽一些孔,在每個孔中注入新的代碼流。因此AOP實現的關鍵是將advice的代碼嵌入到主體程序之中,術語稱編織(weaving)。這是很自然的——將問題分解之後再合成,問題才得以還原。編織可分兩種:一種是靜態編織,通過修改源碼或字節碼(bytecode)在編譯期(compile-time)、後編譯期(post-compile)或加載期(load-time)嵌入代碼——請注意,這裡涉及到剛才提到的元編程和產生式編程;另一種是動態編織,通過代理(proxy)等技術在運行期(run-time)實現嵌入。具體的工具包括一些擴展性語言如AspectJ、AspectC++等和一些框架如AspectWerkz、Spring、Jboss AOP等。”
歎號搔著頭:“聽起來怪復雜的。”
引號倒不在乎:“這些機理是AOP的實現者需要操心的,使用者只需關心AOP是否好用,性能如何等等。”
“為了讓你們有更直觀的印象,我們借用光學原理來類比。”冒號用幻燈片展示了一幅圖——
“眾所周知,白光經過三稜鏡的折射而分解為七色光,是謂光的色散。再經過一個倒置的三稜鏡,七色光又重新會聚為白光。”冒號簡述中學的物理知識,“如果把一個復雜的系統看作復合色的白光,經過第一個三稜鏡——關注分離器,系統被分解為不同的切面,如同不同的單色的彩光。這些切面經過第二個三稜鏡——編織器,再度合成為原系統。”
歎號臉上的迷惘之色漸去:“這下清楚多了。”
句號積極發言:“從中看出,AOP的實施分三步:切面分解、切面實現和切面合成。其中第一步是在設計者的頭腦中進行的,第三步是通過AOP的工具實現的,真正需要程序員編碼的部分在第二步,即分別實現各切面的advice。”
冒號趕緊補漏:“你好像忽略了切面的另一要素pointcut,如果程序員不指明advice掛靠的切入點,系統如何知道該何時何處調用他編寫的執行代碼呢?”
句號的嘴張成O狀:“是哦,我怎麼把這茬給忘了?”
在AOP的議題結束前,冒號不忘指出:“與OOP一樣,AOP在帶來便利的同時,也增加了一定的復雜度和性能損耗。它們更適用於大中型程序,用在小型程序中則不啻牛刀殺雞。”
,插語
[1] 耦合(coupling)用來衡量模塊之間的依賴程度,聚合(cohesion)用來衡量模塊內在的關聯強度。它們常用來作為軟件質量的評判標准,耦合度宜低,聚合度宜高。
。總結
SoC是Separation of concerns的縮寫,指應將關注點分離;DRY是Don’t Repeat Yourself的縮寫,指應盡量減少重復代碼。
抽象與分解是治愈代碼紊亂、松散、重復的良方。
抽象與分解的原則是單一化和正交化,以保障軟件系統符合“高聚合、低耦合”的要求。
橫切關注點指與程序的縱向主流執行方向橫向正交的關注焦點。
接入點是附加行為——建議(advice)的執行點,切入點(pointcut)是指定的接入點(join point)集合,這些接入點共享一段插入代碼。切入點與建議組成了切面(aspect),是模塊化的橫切關注點。
編織是將附加的切面邏輯嵌入到主體應用程序之中的過程。編織分靜態編織和動態編織兩種。靜態編織在編譯期、後編譯期或加載期嵌入代碼,動態編織則在運行期嵌入。
AOP的實施分三步:切面分解、切面實現和切面合成。
OOP只能沿繼承樹的縱向方向重用,AOP可以沿橫向方向重用。
語言之間的天然差別,譯者的專業水准、語言功底和嚴謹程度以及時間上的滯後決定了閱讀原文書籍和資料的必要性。