面向方面 编程_面向方面的编程:它有什么用?

最近,我被要求为我们的软件工程研究小组(SERG)进行面向方面编程(AOP)的讨论。 会议开始前几个小时,一位学生问我:“那么,方面有什么好处?不要给我记录示例。当我阅读有关方面的内容时,这似乎是我唯一看到的东西。”

他的问题使我停下脚步,考虑了在我开发的某些软件系统中应用AOP的有效方法。 这也让我意识到,我们需要考虑如何以及何时采用新方法,尤其是当它们需要新的思维方式时。 我在本专栏前面已经讨论过,AOP似乎代表了一种新方法。 我想谈谈我认为可以有效(并且已经)使用AOP的一些方法。 我们还将介绍AOP的一些最新进展,这些进展可能有助于其采用。

在讨论中,我将使用面向方面的Java作为工具。 但是,今天有几种语言的面向方面的实现。 其中包括AspectC ++甚至AspectL,这是面向方面的Lisp实现。 1个

AOP概念回顾

如果您不熟悉AOP,则有很多介绍性文章,包括我2004年2月的文章。 2 AOP的许多(如果不是大多数)介绍都使用日志记录为例来说明方面的概念。 (记录是很多人理解的东西,它是如何使用AOP的一个很好的例子。)方面是横切的关注点。 也就是说,它们不容易封装在单个类中。 但是,如果我们严格遵循面向对象的范式,则需要以统一,可维护的方式来表示此类问题。 这通常涉及将横切职责委派给单独的帮助程序类,并依赖于需要关注所表达的功能的每个类,以便在适当的位置包括对委托的调用。 确保开发人员始终在代码中的适当位置插入登录名是很难执行的。 方面提供了一种机制,尽管它并不完美,但是可以改善情况。

为了欣赏有关AOP的讨论,您需要了解一些概念。 主要概念是连接点 。 这是所有程序员都熟悉的概念,但是现在我们有了一个名字。 连接点是“程序流程中定义明确的点”。 3连接点有很多类型,例如方法调用或方法返回,它们可以是正常返回或引发的异常。

使用AOP,我们需要一种方法来识别程序中的连接点。 AspectJ使用切入点描述一个或多个连接点。 切入点是一个描述一组连接点的表达式。 您可以将切入点视为对代码的查询,该查询返回一组连接点。

选择一组连接点时,将为其提供建议 。 咨询是可执行代码,在程序运行期间遇到连接点时运行。 连接点,切入点和建议可解决软件的动态属性。 建议会更改程序代码的运行特征。

有一个概念可以解决系统的静态特性。 这是inter-type声明 。 类型间声明允许您更改程序的静态结构。 您可以添加方法和变量,并根据特定规则更改继承层次结构。

正如类是Java的模块化单元一样,方面是AspectJ的另一个模块化单元。 该方面封装了连接点,切入点,类型间声明和有关横切关注点的建议。 AOP不能替代面向对象的分析和设计。 它通过解决OO方法不能充分提供最理想的解决方案的情况而建立在OO范式的基础上。

AOP示例

现在,让我们看一下可以使用AOP的示例。 在生产系统中可以找到一些示例,在生产和开发环境中可以找到其他示例。 让我们从开发人员的两个示例开始。

执行跟踪

令我惊讶的是,有多少开发人员在他们的代码中放置了某种打印语句来调试或跟踪程序的执行。 我发现调试器擅长提供此信息。 但是我们不是在这里讨论了解调试器的优点。 想要对程序产生某种文本跟踪当然有正当的理由。 Eclipse中当前的AspectJ开发工具(AJDT)集提供了一个很好的示例,该方面实现了程序执行跟踪。 Eclipse帮助中详细描述了该示例。 您可以在AspectJ编程指南中找到它。

该示例具有一个小型Java应用程序,该应用程序具有用于表示二维图形(例如圆形和正方形)的类。 它还有一个主要方法,可以创建两个圆和一个正方形,并打印出它们的属性,例如面积,周长以及其中心点之间的距离。 运行该程序时,将得到如图1所示的输出。

图1:来自形状程序的输入

图1:来自形状程序的输入

如果要查看调用的方法的实际顺序,我们有两种选择。 在第一种方法中,我们可以在每个方法的开头插入代码,该代码将显示一条消息,其中包含方法的名称和类,以及输入了该方法的事实。 对于每种方法,我们都必须这样做。 在第二种方法中,我们将创建一个方面将执行完全相同的操作。 使用这种方法,我们不必更改任何应用程序代码。

跟踪示例由使用方面的跟踪解决方案的多个版本组成。 我们将看最终版本。 有关其他版本的讨论,请参见AspectJ编程指南。

强大的跟踪机制的解决方案包括两个文件。 首先是抽象方面。 这类似于抽象类,其中一些代码留给程序员在派生类中实现,或者在这种情况下是派生方面。 这个称为Trace的抽象方面具有几种标准方法,这些标准方法用于打印有关进入和退出方法或构造函数的消息,以及用于格式化输出的消息。 如果不使用方面,则这些方法将在用于输出跟踪信息的帮助器类中。 跟踪还允许程序员通过设置称为TRACELEVEL的属性来设置跟踪的类型。 共有三个级别:无消息,不缩进的消息和缩进以显示嵌套调用的消息。

跟踪定义了三个切入点; 其中的两个是具体的,一个是抽象的,如图2所示。抽象切入点myClass必须由扩展Trace的任何方面提供。 切入点的目的是为包含将建议的连接点的对象选择类。 这使开发人员可以决定要在跟踪输出中包括哪些类。

图2:跟踪方面的切入点

图2:跟踪方面的切入点

myConstructor切入点在myClass选择的类中的对象的任何构造函数的开头选择连接点。 连接点是实际的构造函数主体。 myMethod与myConstructor相似,但是它选择所选类中任何方法的执行。 还请注意,由于建议中使用了toString方法,因此它省略了toString方法的执行。

该方面提供的建议非常简单。 有在每个连接点之前注入的建议,以及在连接点之后执行的建议。 如图3所示。

图3:Trace方面的建议

图3:Trace方面的建议

为了使用“跟踪”方面,您需要对其进行扩展并提供抽象切入点的具体实现。 图4显示了示例程序的TraceMyClasses方面的主体。 切入点表示仅选择作为TwoDShape,Circle或Square实例的对象。 main方法设置TRACELEVEL,初始化跟踪流,并运行示例的main方法。

图4:具体的跟踪方面

图4:具体的跟踪方面

图5显示了运行示例的部分输出。 注意,输出显示有关每个对象的信息。 这是每个对象的toString方法的一部分。 由于myClasses切入点将对象发布到建议,因此建议可以轻松添加有关对象的信息。

图5:示例跟踪输出

图5:示例跟踪输出

与在需要的任何地方手动插入跟踪代码相比,AOP跟踪方法有什么好处? 有几个。

  • 您在一个(两个方面)中拥有与跟踪关注点相关的所有源代码。
  • 插入和删除跟踪代码很容易。 您只需从构建配置中删除方面。
  • 跟踪代码随处可见,即使您向目标类添加新方法也是如此。 这消除了人为错误。 您还知道所有跟踪代码都已删除,并且从构建配置中删除方面时,您并没有丝毫忽略。
  • 您具有可以应用和增强的可重用方面。

按合同设计或防御性编程

Bertrand Meyer介绍了“ 按合同设计”的概念。 4该原理断言,一个类的设计者和该类的用户共享有关该类实现的假设。 合同包括不变式,前提条件和后期条件。 通过合同设计可以使类设计者将精力集中在实现类功能的逻辑上,而不必担心参数的有效性。 当然,这是在合同中规定了论点的前提条件的情况下。 只要所有类别的客户都遵守合同,按合同设计可以避免额外的代码并提高性能。

当构建广泛使用的库时,您可能无法对传递给您的方法的参数的有效性进行假设。 在继续每种方法的逻辑之前,您需要检查参数。 这是防御性编程的一个例子。 您假设可能会出错的任何事情都会发生,并且您可以优雅地处理它。

假设您将采用简单的Shapes程序并将其公开使用。 您要确保所有坐标都在第一个欧几里德象限中,也就是说,x和y坐标为非负数。 如果这些点也要在窗口的坐标中表示,则这是一个有效的约束条件,因为大多数窗口系统都以(0,0)左上角的点开始,并向右和y方向增加x坐标-向下移动时进行协调。 为了满足您的内部需求,您希望使用按合同设计,因为您可以控制组织中使用类的开发人员。 在将其发布给外部客户端时,您想要检查参数并在参数无效的情况下引发异常。 方面提供了一种优雅的方式来实现您所需要的。

我们将构建一个方面来检查公共方法中的所有参数。 我们要做的第一件事是构造切入点。 我们将使用上一个示例中的myClass切入点,并添加切入点以选择需要参数检查的构造函数,并使用distance方法确保不使用空值调用它。 图6显示了我们需要的一组切入点。 请注意,第二个切入点指定切入点的目标是TwoDShape的实例。 这意味着此切入点仅选择对此类对象中的distance方法的调用。

图6:用于参数检查的切入点

图6:用于参数检查的切入点

最后,我们需要添加适当的建议。 为简单起见,对于构造函数,我们希望在遇到无效参数时打印一条消息,然后将实际值更改为零,而在传递空值时忽略对距离的调用。 图7显示了这两个建议项。

图7:参数检查建议

图7:参数检查建议

当我们尝试执行以下语句时:

Circle c3 = 新的 Circle(3.0,2.0,-2.0);

c1.distance( null> );

在我们的程序中,我们得到以下输出:

Negative argument encountered at: execution(tracing.Circle(double, double, 
	double)) All arguments changed to 0.0
Null value given to distance at: 
	call(double tracing.Circle.distance(TwoDShape))

通过显示确切的行号和源文件名,我们可以对错误消息进行更多处理,但是此示例显示了主要技术。

在一个拥有多个类并公开多个接口的大型项目中,可以针对实现参数检查的各个方面使用单独的目录来组织代码。 我可以想象组织这些方面的几种方法,以使它们易于识别和维护。 当构建供内部使用的系统时,您将使用内部构建配置,当构建供​​外部使用的系统时,您将使用包含各个方面的配置。 Eclipse AJDT使创建新构建配置变得简单。

方面和设计模式

设计模式已经成为平常的好节目。 AOP可能为我们提供了一种改进现有模式并发现新模式的方法。 实际上,为横切关注点注入代码是一种模式。 当前,一些研究人员正在评估使用AOP方法的设计模式的实现。 不列颠哥伦比亚大学的Jan Hannemann一直在研究该主题,并将其作为博士学位。 研究。 5 Nicholas Lesiecki还写了有关IBM developerWorks的方面和设计模式的文章。 6查看他的文章,以进行比我这里提供的更为详细的讨论。

让我们看一个非常简单的示例,说明如何在AspectJ中实现标准设计模式(适配器模式)。

图8显示了适配器模式的统一建模语言(UML)图。 在这种模式下,客户端需要服务并提出请求。 该服务可能有很多提供者,并且每个提供者都有不同的名称或服务请求者必须遵守的其他一些非标准要求。 良好的面向对象设计建议我们将对服务的请求封装在目标接口中,对接口进行编程,并根据需要构建适配器,以充当客户端和服务之间的中介者(图中的Adaptee)。 。

图8:适配器模式

图8:适配器模式

适配器的方法似乎非常合理和合理。 但是,如果您的旧系统没有设计为使用适配器之类的模式,该怎么办? 最初的设计师没有意识到未来改变的可能性。 同样,对该服务的调用可能会遍及整个应用程序。 您如何实施一项新的,改进的服务? 如果没有方面,您可能会重构系统,找到对服务的每个调用,设计一个适配器,并实现Adapter模式。 根据调用旧服务的地方数量,这可能是一项艰巨的任务。 重构以改进代码是一个值得追求的目标。 但是,我们并不总是那么奢侈。 在时间限制下,我们必须尽力发展代码。

在这种情况下,我们可以构建Adapter模式的AOP版本,该版本将解决紧迫的问题,并朝着更加组织化,设计更好的系统迈出了一步。

使用服务的简单客户端如图9所示。该服务将返回从一个点到当前Client对象的距离。

图9:简单客户端

图9:简单客户端

服务的接口描述了一种方法:

double useService(Client clt, double x, double y);

新的,经过改进的服务提供者具有不同名称的方法,具有不同的参数。 它的界面是:

double useNewService(double x0, double x1, double y0, double y1);

我们希望构建一个适配器,该适配器将位于对旧服务的调用之间,为新服务获取正确的参数,调用新服务,然后将结果返回给客户端-而无需更改客户端。 我们这样做的方面如图10所示。

图10:调用新服务的适配器方面

图10:调用新服务的适配器方面

注意方面是多么简单。 我们声明一个切入点,以标识对原始服务的useService方法的每次调用。 然后,在从主叫客户端中提取所需信息之后,我们使用环绕建议将呼叫替换为对新服务的呼叫。

这种方法有优点也有缺点。 首先,我们可以通过这一简单的方面对应用程序中的所有代码进行更改。 我们还封装了在一个地方调用服务的担忧。 现在,如果新服务被新服务取代,我们只需要更改此方面的代码即可。 这些无疑是使系统更强大的好处。

主要的缺点是,即使不使用旧客户端,它仍在系统中。 如果有时间,我们很可能会回过头来重构系统,以使用更标准的适配器模式(似乎从未有过)。 至少,我们可以使用返回伪值(如0.0)的代码来修改原始服务的useService方法,因为它将永远不会被调用。

工业强度用途

到目前为止,我们已经研究了应用AOP的相当有限但有用的示例。 我们可能会问技术扩展的程度如何。 我将在这里指出并举一个简单的例子。 更详细的外观将需要本专栏中的另一篇文章。

你们中有些人可能熟悉Spring框架。 7 Spring框架是一个支持开发企业应用程序的应用程序框架。 它提供了用于构建复杂企业应用程序的分层J2EE框架。 Spring中使用的基本技术之一是AOP。 Spring开发了自己的面向方面的实现,该实现允许将横切逻辑在运行时应用于代码,而不是像AspectJ一样通过编译器来实现。 但是,它提供了集成功能,使您可以轻松地将AspectJ与Spring框架合并。

当前,许多组织正在使用Spring来构建更好的企业应用程序,这些应用程序设计合理且需要较少的开发时间。 据我估计,这是一个很好的例子,说明了我们如何将方面应用于实际问题。 8

我们从这里去哪里?

我认为,AOP将来将成为软件开发人员工具包的一部分。 我不知道是否可以构建将AOP视为主要设计机制的系统,但是我确实认为我们将找到方法来从各个方面增强OO设计。 需要做一些事情来帮助加快流程的进行。

首先,我们需要一些稳定的标准AOP实现。 这个过程已经开始。 AspectJ版本5是两种最受欢迎​​的Java AOP语言(AspectJ和AspectWerkz)的合并。 其他语言(例如C ++)的标准实现也将有所帮助。

其次,我们需要开发度量标准,以使我们能够推理将AOP应用于特定系统的有效性。 例如,当我们使用AOP重新实现设计模式时,它们是否比标准的OO模式更好? 在某些情况下它们会更好吗? 为了获得信息并制定这些指标,我们必须采取哪些措施? 天真的方法的时代(我们认为,如果系统是面向对象的(或选择您的技术),那么它就很好了)。 我们需要根据经验证据做出设计和实施决策。 9

第三,我们需要继续开发支持AOP的工具。 我很高兴地说,新的Eclipse AJDT是一套很好的工具。 这些工具不断改善我们为有效和高效使用方面所需的支持。 这些工具还必须支持处理方面的新流程。

方面仍然存在。 它们距离成为主流应用程序还有一段距离,但它们每天都在变得越来越近。 我建议您看一下它们,并领先一步。

翻译自: https://www.ibm.com/developerworks/rational/library/mar06/pollice/index.html

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值