冒号和他的学生们
——程序员提高班纪事
- 切面范式
横看成岭侧成峰 ——《苏轼·题西林壁》
引号重开话题:“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)。此原则相当普适,是分析复杂事物的一种基本方法,在数学和物理中应用得尤为广泛,如质因式分解、正交分解、谱分解等等。”
逗号调皮地抬杠:“为什么称为正交化呢?斜交化不行吗?”
冒号呵呵一笑:“互为正交的两个向量在彼此方向上投影为零,意味着彼此独立,互不影响,斜交可不行。”
逗号吐了吐舌头。
“诚如前述,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实现的机理是什么?”
冒号回答:“如果一个程序是一个管道系统,AOP就是在管道上钻一些孔,在每个孔中注入新的代码流。因此AOP实现的关键是将advice的代码嵌入到主体程序之中,术语称编织(weaving)。这是很自然的——将问题分解之后再合成,问题才得以还原。编织可分两种:一种是静态编织,通过修改源码或字节码(bytecode)在编译期(compile-time)前后或加载期(load-time)嵌入代码;另一种是动态编织,通过代理(proxy)等技术在运行期(run-time)实现嵌入。具体的工具包括一些扩展性语言如AspectJ、AspectC++等和一些framework如AspectWerkz、Spring、Jboss AOP等。”
叹号搔着头:“听起来挺复杂的。”
句号说:“这些机理是AOP的实现者需要操心的,使用者只需关心AOP是否好用,性能如何等等。”
冒号表示赞同:“与OOP一样,AOP在带来便利的同时,也增加了一定的复杂度和性能损耗。它们更适用于大中型程序,用在小型程序中则不啻牛刀杀鸡。”