第三课 常见范式(3)
3.3切面范式——多角度看问题
横看成岭侧成峰 ——《苏轼·题西林壁》
关键词:编程范式,SoC,DRY,AOP,Aspect,join point,pointcut,advice,OOP
摘要:切面式编程简谈
!预览
· 从宏观角度看,太阳底下没有新鲜事——AOP无非是SoC原理和DRY原则的一种应用
· 从微观角度看,太阳每天都是新的——AOP虽自OOP的土壤中长出,却脱离藩篱自成一体
· 抽象是前提,分解是方式,模块化是结果
· 在常人眼中复杂的牛体,庖丁经过抽象,已目无全牛,及至提刀分解,自是游刃有余。待牛如土委地,模块化即成
· 两条(抽象与分解的原则):单一化,正交化。每个模块职责明确专一,模块之间相互独立,即高内聚低耦合
· 对程序员来说,英语也是一门计算机语言,而且是必修的语言
· OOP只能沿着继承树的纵向方向重用,而AOP则弥补了OOP的不足,可以在横向方向重用
· 如果一个程序是一个管道系统,AOP就是在管道上钻一些孔,在每个孔中注入新的代码流
?提问
:讲解
课间休息刚一结束,引号便重开话题:“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++、Aspect#等和一些框架如AspectWerkz、Spring、Jboss AOP等。”
叹号搔着头:“听起来怪复杂的。”
引号倒不在乎:“这些机理是AOP的实现者需要操心的,使用者只需关心AOP是否好用,性能如何等等。”
“为了让你们有更直观的印象,我们借用光学原理来类比。”冒号用幻灯片展示了一幅图——
“众所周知,白光经过三棱镜的折射而分解为七色光,是谓光的色散。再经过一个倒置的三棱镜,七色光又重新会聚为白光。”冒号简述中学的物理知识,“如果把一个复杂的系统看作复合色的白光,经过第一个三棱镜——关注分离器,系统被分解为不同的切面,如同不同的单色的彩光。这些切面经过第二个三棱镜——编织器,再度合成为原系统。”
叹号脸上的迷惘之色渐去:“这下清楚多了。”
句号积极发言:“从中看出,AOP的实施分三步:切面分解、切面实现和切面合成。其中第一步是在设计者的头脑中进行的,第三步是通过AOP的工具实现的,真正需要程序员编码的部分在第二步,即分别实现各切面的advice。”
冒号赶紧补漏:“你好像忽略了切面的另一要素pointcut,如果程序员不指明advice挂靠的切入点,系统如何知道该何时何处调用他编写的执行代码呢?”
句号的嘴张成O状:“是哦,我怎么把这茬给忘了?”
在AOP的议题结束前,冒号不忘指出:“与OOP一样,AOP在带来便利的同时,也增加了一定的复杂度和性能损耗。它们更适用于大中型程序,用在小型程序中则不啻牛刀杀鸡。 ”
,插语
[1] 耦合(coupling)用来衡量模块之间的依赖程度,内聚(cohesion)用来衡量模块内在的关联强度。它们常用来作为软件质量的评判标准,耦合度宜低,内聚度宜高。
。总结
“”参考
[1] Wikipedia.Aspect-oriented programming.http://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