第五篇 终点还是起点

    世界上,任何事物都不是完美的,OOP也一样,它不能解决所有的编程问题,OOP在纵向分解问题方面表现十分出色,但对于水平问题,有点无能为力,而AOP恰好能够弥补这方面的不足,我们在第15章将介绍时下最流行的AOP技术。

    了解和使用模式对于开发者来说,还远远不够,软件的核心是模型,开发大型的复杂业务的应用,需要精炼的模型,如果模型不够精炼,也不能享受到设计模式带来的好处,在第16章我们将介绍如何提炼模型解决复杂的领域问题。

    在本篇末尾,第17章,我们将回顾本书的内容。

    第15章 面向切面的编程(AOP)

    OOP目前已经是软件开发的主流了,我们可以非常方便地把领域问题进行分解,封装成对象。然而和过程式编程(Procedural Programming, PP)或者函数式编程语言(Functional Programming, FP)等传统编程技术一样,还有一类问题它们都无法优雅地解决,而产生这些问题的原因正是由于它们的共同优点(把问题分解为小的单元——对象、方法、结构体等)引起的。进而引入最流行的概念之一:面向切面的编程(AOP)。本章我们首先给出一个例子引入这类问题的讨论。

    本章主要包括如下内容。

    为什么使用AOP。

    AOP的一些重要概念。

    AOP的一些流行框架。

    AOP联盟。

    AOP技术的风险以及AOP与OOP之间的关系。

    15.1 记录时间

    我们依然采用大家熟悉的回家过年的例子来描述该问题,以第2章的示例代码为基础,假如我们现在需要记录travel()方法所耗费的时间,可以使用模板方法模式来解决,重构代码大致如下所示。

    figure_0226_0236

    代码注解

    我们使用Stopwatch类记录travel()的运行时间。Stopwatch(秒表)类正如其名,它从调用reset()方法时开始计时,当调用info()方法时会打印开始计时到当前操作这段执行时间的长度。

    为了记录travel()方法的执行时间,我们把这个方法分解为三个部分,分离出的抽象方法onTravel()方法代替之前travel()方法,执行之前该方法的全部逻辑;在执行onTravel()方法之前设定计时时刻;在执行该方法之后打印出这段时间的长度。

    那么以PassengerByAirAop类为例,onTravel()方法代码大致如下。

    figure_0227_0237

    为了方便计算时间,让线程休息200毫秒,即:TimeUnit.MILLISECONDS.sleep(200l),其他子类与之类似,我们在这里将不再赘述。

    我们撰写的测试代码如下所示。

    figure_0227_0238

    figure_0228_0239

    某一次的测试执行结果如下所示。

    figure_0228_0240

    figure_0229_0241

    模板方法模式解决了对onTravel()方法运行时间的统计问题,如果我们想更进一步对subscribeTicket()和celebrate()所耗的时间进行统计,那该怎么办?我们只能为每个方法添加了如下斜体加粗部分的代码,如下所示。

    figure_0229_0242

    冗余代码的“臭味”再度摆在我们面前了,到目前为止,还没有一个已经介绍的模式能够解决这类重复的代码问题。使用模板方法模式对少数几个方法进行这样的处理,似乎问题不会太严重,但如果对几十个,甚至上千个方法进行同样处理,重复代码的“臭味”将会弥漫在整个程序中。

    有没有更加优雅的方案把这些零散的代码模块化在一处呢?答案是有的,这即是我们下节要介绍的AOP技术。