1. 面向Aspect的编程(AOP)发展背景
1.1. 软件编程方法学的发展
在计算机科学发展的早期阶段,开发人员利用直接的机器编码方式来编写程序。然而大量的时间被花费到考虑机器指令集,而不是问题本身。渐渐地,对底层机器指令集进行了某些抽象的高级语言开始出现。然后出现了结构化语言,使得人们可以根据任务执行的过程来分解问题。但随着问题复杂化的增加,我们需要有更好的技术来帮助人们解决问题。当前,我们可利用面向对象的编程(OOP)技术,将系统看成一组协同对象,利用类来隐藏接口内部的实现细节,多态为相关的类提供了一组公共的行为和接口,允许具体化的构件无需访问基类的实现就可以改变一个特定的行为。
编程方法学与编程语言定义了我们与机器之间的通信方式。每一种新的方法学都提供了一些新的方法来分解问题,例如依赖机器的代码、独立于机器的代码、过程、类等等。每一种新的方法学都提供了一种更为自然的方式来将系统需求映射为编程结构,这些编程方法学的不断发展可以让我们创建复杂度更高的系统。反之亦然,即我们允许复杂度越来越高的系统存在,因为新的技术可以帮助我们来处理这种复杂性。
当前,OOP已经成为大多数软件开发项目的选择。实际上,当OOP开始对公共行为进行建模时,就已经展示出其强大的功能。然而在下面我们将看到OOP并不能够完全解决跨越多个模块的行为,这些问题可能你也有过亲身体验。我们也将看到面向Aspect的编程(AOP)填补了这个空缺,而且在编程方法学的发展过程中AOP很可能向前迈了一大步。
1.2. 将系统看作一组Concern
我们可以将系统看作由一组Concern组成。那么什么是concern呢? concern就是一个特殊的目标、概念或感兴趣区。从技术上来讲,一个典型的软件系统是由许多个核心级concern和系统级concern组成。例如,一张信用卡处理系统包括处理支付的核心concern,以及处理日志、交易一致性、鉴别用户身份、安全、性能等系统级concern。这些concern都是一些横切concern,而大多数concern都会影响到多个模快。如果使用目前的编程方法学,那么这些横跨多个模快的横切concern将使系统很难设计、理解、实现以及发展。
我们将一个复杂的软件系统看作是由多个concern组合在一起而实现的,一个典型的系统可能由业务逻辑、性能、持久数据、日志、调试、鉴别用户身份、安全、多线程间的安全、检查错误等多种concern组成。同样地,在开发过程中我们也将遇到许多concern,例如可理解性、可维护性、可跟踪性、可发展性等等。图1描述了由不同模快实现的一组concern。
图1: 由模快实现一组concern
图2将一组需求看作是一束通过棱镜传输的光线。我们将一个需求光束通过一个可以标识concern的棱镜进行传输,棱镜将把每一个concern区分开。这个视图也可以扩展到开发过程中的concern。
图2:棱镜模拟下的Concern分解
1.3. 系统中的横切concern
开发人员将根据多个需求来创建系统,我们可以将这些需求划分为核心模块级需求和系统级需求。许多系统级的需求相互之间以及与核心模块级需求一般是正交关系(即互相独立),而且系统级需求一般横切多个核心模块。例如,一个典型的企业应用一般由鉴别用户身份、日志、资源池、管理(administration)、性能、存储管理(storage management)等多个横切concern组成。每个concern都要横切多个子系统,例如存储管理concern将影响每个有状态的业务对象。
public class SomeBusinessClass extends OtherBusinessClass {
// Core data members
// Other data members: Log stream, data-consistency flag
// Override methods in the base class
public void performSomeOperation(OperationInformation info) {
// Ensure authentication
// Ensure info satisfies contracts
// Lock the object to ensure data-consistency in case other threads access it
// Ensure the cache is up to date
// Log the start of operation
// ==== Perform the core operation ====
// Log the completion of operation
// Unlock the object
}
// More operations similar to above
public void save(PersitanceStorage ps) {
}
public void load(PersitanceStorage ps) {
}
}
下面考虑一个简单的,但也是更具体的例子。我们来考虑封装某个业务逻辑的类的实现框架。
在上面的代码中,我们至少要考虑三件事情。首先是要考虑不属于这个类核心concern的其它数据成员;其次performSomeOperation()方法的实现中执行了许多非核心的操作,例如需要处理日志、鉴别用户身份、多线程间的安全、验证合同、cache管理等concern,而且在其它类中同样也应用到这些concern;第三,不清楚是否执行持久性管理的save()和load()方法是否应属于该类的核心部分。
1.4. 横切concern引起的问题
虽然横切concern跨越多个模快,但目前的技术上通常用一维方法学来实现,这就使得从需求到实现是沿着一个单一的维来映射的。这个单一的维通常就是核心模块级实现,其它的需求则与核心模块级实现相互交织在一起。换句话说,需求空间是一个N维空间,而实现空间是一维的空间,这样的不匹配造成了从需求到实现是一个很笨拙的映射。
1.4.1. 问题的表现
采用目前的方法学来实现横切concern已表明存在许多问题,这些问题的表现可以分为两类:
u 代码交织(code tangling):在一个软件系统的模快可能同时与数个需求交互。例如在通常情况下,开发人员同时思考业务逻辑、性能、同步、日志与安全。这样一个多需求交织在一起的状况将导致编程元素中同时存在每一个concern的实现,即代码交织问题。
u 代码分散(code scattering):按照定义,因为横切concern跨越多个模快,所以与这些横切concern相关的实现也跨越所有这些模块。例如,在一个用到数据库的系统中,性能concern可能影响所有访问数据库的模块。
1.4.2. 问题的影响
总而言之,代码交织和代码分散影响到软件设计与开发的许多方面,主要表现在:
u 可跟踪性能差:同时实现多个concern使得每一个concern与其实现之间的对应关系变得模糊,二者之间的映射变得很差。
u 生产力下降:同时实现多个concern使得开发人员将注意力从主要关心的concern转移到外围的concern,造成了生产力的下降。
u 代码重用性差:由于一个模块要实现多个concern,对类似功能有需求的其它系统不能很方便地重用该模块,进一步造成了生产力的下降。
u 代码质量差:交织在一起的代码含有隐含的问题。而且,一次想要实现多个concern,可能会造成某些concern没有引起足够的重视。
u 很难发展:有限的视野以及受约束的资源通常生成一个只解决目前concern的一个设计方案,对未来需求提供的解决方案通常是推倒重新实现。由于先前的实现不是按模块化来组织,因而需要修改多个模块才可以解决问题。每一次改变都要修改每个子系统,这将造成系统的不一致性,而且需要大量的测试工作以确保重新实现不会引起Bug。
1.4.3. 目前提出的解决方法
由于许多系统都有横切concern,因而也出现了一些技术来使得系统的实现模块化。这些技术包括mix-in classes、设计模式、与特定应用领域相关的解决方法。
采用mix-in class技术,可以推迟一个concern的实现。主要的类包含一个mix-in类实例,并允许系统的其它部分来设置该实例。例如在信用卡处理系统中,实现业务逻辑的类包含一个logger mix-in。系统的另一部分可以设置该logger来获取相应的logging类型。例如该logger可以被设置为采用文件系统或消息中间件来实现。虽然推迟了logging,但在所有的记录日志点组合类都不包含激活logging操作的代码,也不控制日志记录信息。
与设计模式中的visitor模式和模板方法模式类似,行为设计模式可以推迟实现。然而与mix-in中的情况相同,对操作的控制,即激活visiting逻辑或激活模板方法都留在了主类中。
与特定应用领域相关的解决方法——例如框架和应用服务器——可以使开发人员以模块化方式解决一些横切concern。例如,企业JavaBeans (EJB)架构解决了诸如安全、管理、性能、由容器管理的持久性等横切concern。Bean开发人员只关心业务逻辑,而部署开发人员只关心部署事宜,例如将bean数据映射为数据库,Bean开发人员无需关心数据的存储事宜。这里,用XML映射描述符来实现数据持久性横切concern。
与特定应用领域相关的解决方法为解决特定的问题而提供了一种特定的解决方案。该方法的一个缺点是开发人员面对每一种解决方案都需要学习新的技术。而且,由于这些解决方案是应用于特定领域,因而并没有直接解决横切concern引起的问题,而是要依靠特殊的解决方法。
1.5. 系统架构师面临的难题
好的系统架构师会考虑到当前及未来的需求,以避免系统的实现需要不断打补丁。但这里有一个问题,即对未来的预测是十分困难的。一方面,如果没有考虑未来的横切需求,那么就可能需要在今后修改或重新实现系统的大部分。而另一方面,如果过多考虑出现概率很低的需求,就会增大系统的设计量,使系统混乱而且臃肿。因而系统架构师面临这样一个两难的困境:设计量需要多大?设计时应当考虑的多一些还是少一些?
例如,系统架构中是否应包含一个最初阶段不需要的日志系统?如果答案是肯定的,那么在哪里记录日志?记录什么样的日志信息?类似的难题在与优化系统性能相关的需求中也会出现,因为我们无法预先知道系统的瓶颈所在。通常的方法是先建立系统,构造出其部分概貌,然后采用优化改造来提高系统的性能。这种方法潜在地需要改变由系统概貌所指示的系统的多个部分,而且随着时间的推移,使用方式的改变又将引起新的系统瓶颈。而考虑可重用的库,对系统架构师而言则是更加困难的任务,因为他会发现很难考虑可重用库的所有应用环境。
总之,系统架构师很少会知道系统中要考虑的每一个可能的concern。即使预先知道了系统需求,实现系统所需的细节也不可能很完整,因而系统架构面临着设计过多或过少的两难困境。