理解AOP之一

先来看一个实际开发中的例子,我们常常需要对系统中的某些方法进行日志记录,需要日志记录的方法常常散布在几十个类中。面对这种需求,传统方法就是创建一个基类(或接口),将日志的功能放在其中,并让所有需要日志功能的类继承这个基类(或接口)。

如果这个需求是后期提出的。需要修改的地方就会分散在几十个文件。这样大的修改量,无疑会增加出错的几率,并且加大系统维护的难度。

人们认识到,OOP程序设计经常表现出一些不能自然地适合单个程序模块或者几个紧密相关的程序模块的行为:例如日志记录、对上下文敏感的错误处理、性能优化以及设计模式等等,我们将这种行为称为“横切关注点(cross cutting concern)”,因为它跨越了给定编程模型中的典型职责界限。

一个关注点(concern)就是一个特定的目的,一块我们感兴趣的区域。从技术的角度来说,一个典型的软件系统包含一些核心的关注点和系统级的关注点。开发人员建立一个系统以满足多个需求,我们可以大致地把这些需求分类为核心模块级需求和系统组需求。很多系统级需求一般来说是相互独立的,但它们一般都会横切许多核心模块。一个典型的企业应用包含许多横切关注点,如验证、日志、资源池、系统管理、性能及存储管理等,每一个关注点都牵涉到几个子系统,如存储管理关注点会影响到所有的有状态业务对象。举个例子来说,一个信用卡处理系统的核心关注点是借贷/存入处理,而系统级的关注点则是日志、事务完整性、授权、安全及性能问题等,使用现有的编程方法,横切关注点会横越多个模块,结果是使系统难以设计、理解、实现和演进。



这是一个封装了业务逻辑的类的实现框架:

[code]
public class SomeBusinessClass extends otherBusinessClass
{
//核心资料成员
//其它资料成员:日志流,保证资料完整性的标志位等
//重载基类的方法
public void performSomeOperation (OperationInformation info) {
//安全性验证
//检查传入资料是否满足协议
//锁定对象以食品店当其它线程访问时的资料完整性
//检查缓存中是否为最新信息
//记录操作开始执行时间
//执行核心操作
//记录操作完成时间
//给对象解锁
}
//一些类似操作
public void save(PersitanceStorage ps){ }
public void load(PersitanceStorage ps){ }
}
[/code]


在上面的代码中,我们注意到三个问题:首先,其它资料成员不是这个类的核心关注点;第二、performSomeOperation()的实现做了许多核心操作之外的事,它要处理日志、验证、线程安全、协议验证和缓存管理等一些外围操作,而且这些外围操作同样也会应用于其它类;第三,save()和load()执行的持久化操作是否构成这个类的核心是不清楚的。

由于多数系统中都包含横切关注点、自然的已经形成了一些技术来模块化横切关注点的实现,这些技术包括:混入类、设计模式和面向特定问题域的解决方式。

使用混入类,你可以推迟关注点的最终实现.基本类包含一个混入类的实例,允许系统的其它部分设置这个实例。举个例子来说,实现业务逻辑的类包含一个混入的logger,系统的其它部分可以设置这个logger已得到合适的日志类型,比如logger可能被设置为使用文件系统或是消息中间件。在这种方式下,虽然日志的具体实现被推迟,基本类还是必须在所有写日志的点调用日志操作和控制日志信息的代码。

行为型设计模式,如Visitor和Template Method模式,也允许你推迟具体实现。但是也就像混入类一样,操作的控制——调用visitor或template Method的逻辑——仍然留给了基本类。

面向特定问题域的解决方式,如框架和应用服务器,允许开发者用更模块化的方式处理某些横切关注点。比如E J B(Enterprise JavaBean)架构,可以处理安全、系统管理、性能和容器管理的持久化(container-managed persistence)等横切关注点。B e a n与数据库的映像,但是大多数情况下,开发者还是要了解存储结构。这种方式下,你用基于XML的映像关系描述器来实现于资料持久化相关的横切关注点。它的缺点是对于每一种这样的解决方式开发人员都必须重新学习,另外,由于这种方式是特定问题域相关的,属于特定问题域之外的横切关注点需要特殊的对待。

设计师的两难局面

好的系统设计师不仅会考虑当前需求,还会考虑到可能会有的需求以避免到处打补丁。这样就存在一个问题预知将来是很困难的,如果你漏过了将来可能会有的横切关注点的需求,你将会需要修改或甚至是重新实现系统的许多部分;从另一个角度来说,太过于关注不一定需要的需求会导致过分设计的、难以理解的、臃肿的系统。所以系统设计师处在这么一个两难局面中:怎么设计算不了过分设计?应该宁可设计不足还是宁可过分设计?

举个例子来说,设计师是否应该在系统中包含现在并不需要的日志机制?如果是的话,哪里是应该写日志的点?日志应该记录那些信息相似的例子还有关于性能的优化问题,我们很少能预知瓶颈的所在。常用的方法是建立系统,profile它,然后翻新系统以提高性能,这种方式可能会依照profiling而修改系统的很多部分。此外,随着时间的流逝,由于使用方式的变化,可能还会产生新的瓶颈。类库设计师的任务更困难,因为他很难设想出所有对类库的使用方式。

总而言之,设计师很难顾及到系统可能需要处理的所有关注点。即使是在已经知道了需求的前提下,某些建立系统时需要的细节 也可能不能全部得到,整体设计就面临着设计不足/过分设计的两难局面。

利用AOP分离软件关注点

AOP, 从其本质上讲,使你可以用一种松散耦合的方式来实现独立的关注点,然后 组合这些实现来建立最终系统、用它所建立的系统是使用松散偶合的,模块化实现的横切关注点来搭建的、与之对照 用OOP建立的系统则是用松散耦合的模块化实现的一般关注点来实现的。在AOP中,这些模块化单元叫“方面(aspect)”,而在OOP中,这些一般关注点的实现单元叫做类。

AOP为开发者提供了一种描述横切关注点的机制,并能够自动将横切关注点织入到面向对象的软件系统中,从而实现了横切关注点的模块化.通过划分Aspect代码,横切关注点变得容易处理。开发者可以在编译时更改、插入或除去系统的Aspect,甚至重用系统的Aspect。

更重要的是,AOP可能对软件开发的过程造成根本性的影响。我们可以想象这样一种情况:OOP只用于表示对象之间的泛化一特化(generalization-specialization)关系(通过继承来表现),而对象之间的横向关联则完全用AOP来表现。这样,很多给对象之间横向关联增加灵活性的设计模式(例如Decorator、Role Object等)将不再必要。

AOP 包括三个清晰的开发步骤:

方面分解:分解需求撮出横切关注点。在这一步里,你把核心模块级关注点和系统级的横切关注点分离开来、就前面所提到的信用卡例子来说 你可以分解出三个关注点:核心的信用卡处理、日志和验证。

关注点实现:各自独立的实现这些关注点,还用上面信用卡的例子,你要实现信用卡处理单元、日志单元和验证单元。

方面的重新组合:在这一步里,方面集成器通过创建一个模块单元—一方面来指定重组的规则, 重组过程—一也叫织入(weaving)或结合(integrating)——则使用这些信息来构建最终系统、还拿信用卡的那个例子来说 你可以指定(用某种AOP的实现所提供的语言)每个操作的开始和结束需要记录,并且每个操作在涉及到业务逻辑之前必须通过验证。



AOP与OOP最重要的不同在于它处理横切关注点的方式.在AOP中 每个关注点的实现都不知道其它关注点是否会“关注”它,如信用卡处理模块并不知道其它的关注点实现正在为它做日志和验证操作。它展示了一个从OOP转化来的强大的开发范型。

注意:一个AOP实现可以借助其它编程范型作为它的基础,从而原封不动的保留其基础范型的优点。例如,AOP可以选择OOP作为它的基础范型,从而把OOP善于处理一股关注点的好处直接带过来。用这样一种实现,独立的一般关注点可以使用OOP技术、这就像过程型语言是许多OOP语言的基础一样。

AOP语言剖析

就像其它编程范型的实现一样,AOP的实现由两部分组成:语言规范和实现。语言规范描述了语言的基础单元和语法;语言实现则按照语言规范来验证代码的正确性,并把代码转成目标机器可执行的形式。

AOP语言规范

从抽象的角度看来 一种AOP语言要说明下面两个方面:

关注点的实现:把每个需求映像为代码,然后,编译器把它翻译成可执行代码。由于关注点的实现以指定过程的形式出现,你可以使用传统语言如C、C++、JAVA等。

织入规则规范:怎样把独立实现的关注点组合起来形成最终系统呢?为了这个目的 需要建立一种语言来指定组合不同的实现单元,以形成最终系统的规则。这种指定织入规则的语言可以是实现语言的扩展,也可以是一种完全不同的语言。

AOP语言的实现

AOP的编译器执行两步操作:
1、 组装关注点
2、组装结果转成可执行代码。

AOP实现可以用多种方式实现织入, 包括源码到源码的转换、它预处理每个方面的源码,产生织入过的源码,然后把织入过的源码交给基础语言的编译器, 产生最终可执行代码。比如 使用这种方式,一个基于Java的AOP实现可以先把不同的方面转化成Java源代码,然后让Java编译器把它转化成字节码.也可以直接在字节码级别执行织入; 毕竟 字节码本身也是一种源码。此外,底层的执行系统—一Java虚拟机—一也可以设计为支持AOP的。基于Java的AOP实现如果使用这种方式的话,虚拟机可以先装入织入规则,然后对后来装入的类都应用这种规则、也就是说,它可以执行just-in-time的方面织入。

AOP的好处

AOP可帮助我们解决上面提到的代码混乱和代码分散所带来的问题,它还有一些别的好处:

模块化横切关注点:AOP用最小的耦合处理每个关注点,使得即使是横切关注点也是模块化的。这样的实现产生的系统,其代码的冗余小。模块化的实现还使得系统容易理解和维护。

系统容易扩展:由于方面模块根本不知道横切关注点,所以很容易通过建立新的方面加入新的功能.另外,当你往系统中加入新的模块时,已有的方面自动横切进来,使系统易于扩展。

设计决定的迟绑定:还记得设计师的两难局面吗?使用AOP 设计师可以推迟为将来的需求作决定,因为他可以把这种需求作为独立的方面很容易地实现。

更好的代码重用性:AOP把每个方面实现为独立的模块, 模块之间是松散耦合的.举例来说,你可以用另外一个独立的日志写入器方面来替换当前的,用于把日志写入数据库,以满足不同的日志写入要求。松散藕合的实现通常意味着更好的代码重用性,AOP在使系统实现松散出合这一点上比OOP做得更好。

AspectJ:一个Java的AOP实现

AspectJ是一个可免费获得的、由施乐公司帕洛阿尔托研究中心(Xerox PARC)开发的、Java的AOP实现。它使用java作为单个关注点的实现语言,并扩展Java以指定织入规则。

另外,AspectJ允许以多种方式用方面和类建立新的方面,你可以引入新的资料成员和方法或是声明一个新的类来继承和实现另外的类或接口。

AspectJ的织入器——AspectJ的编译器——负责把不同的方面组合在一起,由于AspectJ编译器建立的最终系统是Java字节码,因此,它可以运行在任何符合Java标准的虚拟机上。而且,AspectJ还提供了一些工具,例如调试器和Java IDE集成等。

我需要AOP吗?

AOP仅仅是解决设计上的缺点吗?在AOP里,每个关注点的实现的并不知道是否有其它关注点关注它,这是AOP和OOP的主要区别。在AOP里,组合的流向是从横切关注点到主关注点,而OOP则相反。但是,OOP可以和AOP很好地共存。比如,你可以使用一个混入类来做组合,既可以用AOP实现,也可以用OOP实现,这取决你对AOP的接受程度,在这两种情况下,实现横切关注点的混入类实现都无需知道它自己是被用在类中还是被用在方面中。举个例子来说,你可以把一个日志写入器接口用作某些类的混入类或是用作一个日志方面,因而,从OOP到AOP 是渐进的。

如果你的系统中涉及到多个横切关注点,你可以考虑进一步了解AOP,了解它的实现和它的好处。AOP很可能会是编程方式的一个里程碑。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值