冒号课堂§3.3:切面范式

冒号课堂

第三课 常见范式(3)

 

3.3切面范式——多角度看问题

横看成岭侧成峰                                            ——《苏轼·题西林壁》

 

关键词:编程范式,SoC,DRY,AOP,Aspect,join point,pointcut,advice,OOP

摘要:切面式编程简谈


预览

·           从宏观角度看,太阳底下没有新鲜事——AOP无非是SoC原理和DRY原则的一种应用

·           从微观角度看,太阳每天都是新的——AOP虽自OOP的土壤中长出,却脱离藩篱自成一体

·           抽象是前提,分解是方式,模块化是结果

·           在常人眼中复杂的牛体,庖丁经过抽象,已目无全牛,及至提刀分解,自是游刃有余。待牛如土委地,模块化即成

·           两条(抽象与分解的原则):单一化,正交化。每个模块职责明确专一,模块之间相互独立,即高内聚低耦合

·           对程序员来说,英语也是一门计算机语言,而且是必修的语言

·           OOP只能沿着继承树的纵向方向重用,而AOP则弥补了OOP的不足,可以在横向方向重用

·           如果一个程序是一个管道系统,AOP就是在管道上钻一些孔,在每个孔中注入新的代码流

 

提问

  • 什么是SoCDRY
  • 如何有效地避免紊乱、松散、重复的代码?
  • 抽象与分解的原则是什么?
  • 什么是横切关注点?
  • 接入点与切入点有何区别?
  • 什么是编织?有哪些不同的编织方法?
  • 实施AOP有哪些步骤?
  • 为什么说AOPOOP的一种补充?
  • 为什么提倡尽可能地阅读原文的书籍和资料?

 

讲解

课间休息刚一结束,引号便重开话题:“OOP方兴未艾,AOP又开始崭露头角。AOP算是OOP的一种补充、一种分支还是一种超越?”

叹号故作捶胸顿足状:“OOP还没有完全吃透,又来了个什么AOP。”

“不同的人对新生事物采取不同的态度。”冒号王顾左右而言他,“追星族倾向于盲目追捧,唯恐落伍,他们信奉新潮的、流行的就是好的;守旧派倾向于本能抗拒,回避求新,他们认为经典的、传统的才是好的。”

引号和叹号互视一眼,不情愿地戴上了老冒派发的帽子。

冒号续道:“从宏观角度看,太阳底下没有新鲜事——AOP无非是SoC原理和DRY原则的一种应用;从微观角度看,太阳每天都是新的——AOP虽自OOP的土壤中长出,却脱离藩篱自成一体,并且嫁接到非OOP的领地,不仅在纯过程式语言、函数式语言、甚至逻辑式语言中得到发展,而且本身也具备了一定的声明式语言特征,成为一种新的软件模块化方式。”

问号举手:“什么是SoCDRY?”

引号代答:“SoC就是Separation of concerns,即关注点分离;DRYDon’t Repeat Yourself,即尽量减少重复代码。”

“答案正确,加十分!”冒号戏赞道,“不良代码通常有两种病征:一是结构混乱,或聚至纠缠打结、或散至七零八落;二是代码重复,叠床架屋、臃肿不堪。治疗此类病症一个有效的方法是抽象与分解:从问题中抽象出一些关注点,再以此为基础进行分解。分解后的子问题主题鲜明且独立完备,既不会牵一发而动全身,也不会四分五裂、支离破碎。同时具有相同特征的部分可以象代数中的公因子一样提取出来,提高了重用性,减少了重复性。”

句号醒悟道:“这不就是模块化吗?”

“准确地说,抽象是前提,分解是方式,模块化是结果。”冒号很讲究精确,“大家记得庖丁解牛的故事吧?在常人眼中复杂的牛体,庖丁经过抽象,已目无全牛,及至提刀分解,自是游刃有余。待牛如土委地,模块化即成。”

句号举一反三:“前面提到的编程范式的基本思想大多不也如此?将程序分别抽象分解为过程、函数、断言、对象和进程,就依次成为过程式、函数式、逻辑式、对象式和并发式。至于泛型式——”

句号讲不下去了。

“泛型式虽未引入新类型的模块,其核心也是抽象出算法后与数据分解。”冒号为其解围,“以此类推,切面式的AOP将程序抽象分解为切面。”

问号提问:“抽象与分解的原则是什么?”

冒号作了个V字:“两条:单一化,正交化。每个模块职责明确专一,模块之间相互独立,即高内聚低耦合high cohesion & low coupling[1]。此原则相当普适,是分析复杂事物的一种基本方法,在数学和物理中应用得尤为广泛,如质因式分解、正交分解、谱分解等等。 ”

逗号调皮地抬杠:“为什么称为正交化呢?斜交化不行吗?”

冒号呵呵一笑:“在数学中互为正交的两个向量在彼此方向上投影为零,意味着彼此独立、互不影响,斜交可不行。 ”

逗号吐了吐舌头。

“诚如前述,AOP以切面为模块。”冒号返回主题,“切面Aspect常直译为‘方面’,但它描述的是横切关注点Cross- cutting concerns),故‘切面’更准确生动,而‘方面’则失之空泛呆板。何谓横切关注点?顾名思义,乃是与程序的纵向主流执行方向横向正交的关注焦点。不妨回顾一下,无论是过程式的函数,还是对象式的方法,都包含了完整的执行代码。但有些代码横跨多个模块,以片断的形式散落在各处,虽具有相似的逻辑,却无法用传统的方式提炼成模块,难以实现SoCDRY。典型的例子如:在调用某些对象的方法、读写某些对象的域、抛出某些异常等等前后,需要用到统一的业务逻辑,诸如日志输出、代码跟踪、性能监控、异常处理、安全检查、事务管理等等。为解决此类问题,AOP应运而生。它将每类横切关注点封装到单独的Aspect模块中,将程序中的一些执行点与相应的代码绑定起来。单个的执行点称为接入点join point),例如:调用某个对象的方法前后;符合预先指定条件的接入点集合称为切入点pointcut),例如:所有以set为命名开头的方法;每段绑定的代码称为一个建议advice)。”

问号有点疑问:“接入点与切入点有何区别?”

冒号释疑:“望文生义,接入处是点,切入处是面,面由点组成。advice定义于切入点上,执行于接入点处。换言之,共享一段附加代码的接入点组成了一个切入点。切入点一般用条件表达式来描述,不仅有广泛性,还有预见性——以后新增的代码如果含有满足切入点条件的接入点,advice中的代码便自动附着其上。这是AOP威力所在,但有时也是麻烦所在。”

引号很较真:“好像一些书上把join point译作连接点,把advice译作通知。”

“误导,完全是误导!”冒号有些痛心疾首,“何谓join point?是advice中额外代码接入之处,join显为‘参加’、‘加入’之意。如果说翻作‘连接’ 只是因缺乏动感和方向性而不够贴切的话,将advice译作‘通知’则近乎荒谬了。advice是在原有程序流程中加入的额外流程,可理解为建议采取的措施,而‘通知’强调的是一种信息,难道是程序运行到join point的信息?抑或采取某种行动的信息?简直不知所云。”

顿了一会,冒号仍意犹未尽:“英文好的技术不好,技术好的英文不好,两者都好的不屑去翻译,导致市面上的译书虽汗牛充栋,然佳作寥寥。这里奉劝各位,如果真想成为优秀的程序员,一定要尽可能地读原文的书籍、文章和文档。事实上,凡是科学和艺术方面的专业人员,要想专业水平上一层台阶,都应读该专业权威经典的原文。要知道,语言之间的天堑原本难以弥合,译者的专业水准、语言功底和严谨程度更是参差不齐。纵使万事俱备,一年半载后的译书便如隔夜的饭菜,虽刚出炉,已然不新鲜了。”

逗号抱怨:“英文虽读得懂,但太慢、太费劲了。”

“多读,读多了就习惯了。”冒号鼓励着,“对程序员来说,英语也是一门计算机语言,而且是必修的语言。”

课堂上有这么一个规律,越是题外的话大家讨论得越欢。下面果然开始嘈杂起来,冒号也乐得乘机歇歇嘴。

等大伙渐渐把视线重新聚焦到讲台上,冒号才继续讲课:“从软件重用的角度看,可以这么理解AOPOOP的关系:OOP只能沿着继承树的纵向方向重用,而AOP则弥补了OOP的不足,可以在横向方向重用。这算是回答了引号开始提出的问题:AOP不是OOP的分支,也不能说是超越了OOP,而是OOP的一种补充——尽管AOP并不局限于OOP。”

问号的求知欲很强:“AOP实现的机理是什么?”

冒号回答:“如果一个程序是一个管道系统,AOP就是在管道上钻一些孔,在每个孔中注入新的代码流。因此AOP实现的关键是将advice的代码嵌入到主体程序之中,术语称编织weaving)。这是很自然的——将问题分解之后再合成,问题才得以还原。编织可分两种:一种是静态编织,通过修改源码或字节码(bytecode)在编译期compile-time)、后编译期post-compile)或加载期load-time)嵌入代码——请注意,这里涉及到刚才提到的元编程和产生式编程;另一种是动态编织,通过代理(proxy)等技术在运行期run-time)实现嵌入。具体的工具包括一些扩展性语言如AspectJAspectC++Aspect#等和一些框架如AspectWerkzSpringJboss AOP等。”

叹号搔着头:“听起来怪复杂的。”

引号倒不在乎:“这些机理是AOP的实现者需要操心的,使用者只需关心AOP是否好用,性能如何等等。”

“为了让你们有更直观的印象,我们借用光学原理来类比。”冒号用幻灯片展示了一幅图——


“众所周知,白光经过三棱镜的折射而分解为七色光,是谓光的色散。再经过一个倒置的三棱镜,七色光又重新会聚为白光。”冒号简述中学的物理知识,“如果把一个复杂的系统看作复合色的白光,经过第一个三棱镜——关注分离器,系统被分解为不同的切面,如同不同的单色的彩光。这些切面经过第二个三棱镜——编织器,再度合成为原系统。”

叹号脸上的迷惘之色渐去:“这下清楚多了。”

句号积极发言:“从中看出,AOP的实施分三步:切面分解、切面实现和切面合成。其中第一步是在设计者的头脑中进行的,第三步是通过AOP的工具实现的,真正需要程序员编码的部分在第二步,即分别实现各切面的advice。”

冒号赶紧补漏:“你好像忽略了切面的另一要素pointcut,如果程序员不指明advice挂靠的切入点,系统如何知道该何时何处调用他编写的执行代码呢?”

句号的嘴张成O状:“是哦,我怎么把这茬给忘了?”

AOP的议题结束前,冒号不忘指出:“与OOP一样,AOP在带来便利的同时,也增加了一定的复杂度和性能损耗。它们更适用于大中型程序,用在小型程序中则不啻牛刀杀鸡。 ”

 

插语

[1] 耦合(coupling)用来衡量模块之间的依赖程度,内聚(cohesion)用来衡量模块内在的关联强度。它们常用来作为软件质量的评判标准,耦合度宜低,内聚度宜高。

 

总结

  • SoCSeparation of concerns的缩写,指应将关注点分离;DRYDon’t Repeat Yourself的缩写,指应尽量减少重复代码。
  • 抽象与分解是治愈代码紊乱、松散、重复的良方。
  • 抽象与分解的原则是单一化和正交化,以保障软件系统符合“高内聚、低耦合”的要求。
  • 横切关注点指与程序的纵向主流执行方向横向正交的关注焦点。
  • 接入点是附加行为——建议(advice)的执行点,切入点(pointcut)是指定的接入点(join point)集合,这些接入点共享一段插入代码。切入点与建议组成了切面(aspect),是模块化的横切关注点。
  • 编织是将附加的切面逻辑嵌入到主体应用程序之中的过程。编织分静态编织和动态编织两种。静态编织在编译期、后编译期或加载期嵌入代码,动态编织则在运行期嵌入。
  • AOP的实施分三步:切面分解、切面实现和切面合成。
  • OOP只能沿继承树的纵向方向重用,AOP可以沿横向方向重用。
  • 语言之间的天然差别、译者的专业水准、语言功底和严谨程度以及时间上的滞后决定了阅读原文书籍和资料的必要性。

 

“”参考

[1] WikipediaAspect-oriented programminghttp://en.wikipedia.org/wiki/Aspect-oriented_programming

[2] Ramnivas Laddad I want my AOP! http://www.javaworld.com/javaworld/jw-01-2002/jw-0118-aspect.html

 

posted on 2009-04-02 22:10  郑晖  阅读(3047)  评论(2编辑  收藏  举报

导航