技术开发 频道

用AspectJ增强设计模式下

    AspectJ 观察者的分析

    我要做的某些设计观察听起来应当很熟悉,因为它们反映了对本文中讨论的前两个模式所做的观察(请参阅 参考资料)。但是,您会注意到,观察者中的设计对比特别突出:

    易于理解:从参与者的视角来看,AOP 版本的观察者更简单,因为它不需要 Song、Playlist 或 BillingService 有什么特殊的东西。从参与者那里消除了这种依赖性,从主要抽象角度来说,让它们更容易理解,同时也使它们更加灵活、更具重用性。例如,它们可以重新用在其他不要求观察者(Observer)模式的系统中。它们也可以参与到其他模式中,而不用担心大量的模式代码会淹没类的核心职责。

    从模式的视角来看,AOP 版本还是更简单一些。AspectJ 实现不要求人们把模式片断汇集在头脑中,而是提供了一个 ObserverProtocol 方面,安排好了模式的结构。然后由具体的方面定义模式应用到这个系统的方式。模块都是很实际的,而且从模式视角来看,模块也很完整。所以,如果正在使用 AJDT,并且注意到(比如)某个观察者方面通知说使用 play() 方法,那么就可以沿着 AJDT 提供的线索理解总体情况。( 参考资料 一节包含一篇优秀文章,其中介绍了 AJDT 如何有助于导航和理解 AspectJ 代码。)

    通知 “事件”的生成在 AspectJ 实现中获得很好的模块化。在传统实现中,这些调用存在于三个不同的位置。不存在能说明为什么某个操作会触发事件,或者其他哪个操作也会这么做的直接线索。在 AOP 模式中,命名切入点沟通了操作的公共属性(标题的用法)。切入点规范指出了模式影响的其他连接点。

    重用:ObserverProtocol 方面的重用潜力非常高。实际上,可重用的模式方面是我编写这篇文章的一半原因。作为开发人员,只要能依赖框架作者的经验,而不是折腾自己的实现,我就会很高兴。方面提供了重用横切代码的能力,这些代码以前纠缠在实现的细节中,根本不是独立存在的。在我分析的组合模式一节中,您会看到重用的一个很好的示例。

    维护:要查看方面让系统更容易发展的具体示例,请回到在系统的 Java 版本中将歌曲从双重计费中排除时遇到的困难上。要用 AspectJ 满足这个需求,只需要编辑 subjectChange() 切入点即可。以下代码可以确保 cflowbelow() 切入点会排除另一个 play() 的控制流程中发生的 play() 执行:

//the original titleUsePointcut pointcut titleUse(Playable playable) : this(playable) && ( execution(public void Playable+.play()) || execution(public void Song.showLyrics()) ); //exclude title usage that occurs as a result //of another title use pointcut topLevelTitleUse(Playable playable) : titleUse(playable) && ! cflowbelow(titleUse(Playable)); //define subjectChange in terms of the more restrictive //pointcut public pointcut subjectChange(Subject subject) : topLevelTitleUse(Playable) && this(subject);

    这个修改是微不足道的,特别是因为它只影响一个文件。而且,它还让新策略的意图变得很清楚。其他修改,例如添加新 Subjects 或主角变化操作,都只需像这样对方面进行简单的修改即可,不需要协调多个文件的修改。

    组合:可以回想一下介绍过的内容,在与应用到(某些)相同的参与者的第二个模式协作时,Java 语言实现的观察者也存在问题。要用 AspectJ 实现这个需求,只要再次用新的切入点和抽象方法定义扩展抽象方面即可。由于每个方面都管理自己的 Observer 列表和 Subject 列表,而且方面也定义了自己的 subjectChange 切入点,所以两个方面不会冲突。每个方面操作时实际上都独立于另一个方面。可以检查清单 4 中 SongCountObserver 的代码:

    清单 4.影响同一个参与者的第二个观察者方面

public aspect SongCountObserver extends ObserverProtocol { declare parents : Song extends Subject; declare parents : SongPlayCounter implements Observer; pointcut titleUse(Song song) : this(song) && execution(public void Song.play()); public pointcut subjectChange(Subject subject) : titleUse(Song) && this(subject); public void updateObserver(Subject s, Observer o) { SongPlayCounter counter = (SongPlayCounter) o; counter.incrementPlays((Song) s); } // could be injected by a dependency injection // framework like Spring private SongPlayCounter defaultCounter = new SongPlayCounter(); pointcut songCreation(Subject s) : execution(public Song.new(..)) && this(s); after(Subject s) returning : songCreation(s){ // different songs could be tracked in different statistical sets... addObserver(s, defaultCounter); } }

    因为在 AspectJ 系统中,多个模式(甚至同一模式的多个实例)可以透明地组合在一起,所以 AspectJ 系统避免了来自“模式密度”的一些问题。这就能够更多地使用设计模式所拥有的非常好的实践,而不用担心实现的重量会压跨代码。(请继续等待 Wes Isberg 在 AOP@Work 系列中即将推出的文章,获取更多使用面向方面技术来避免 OO 设计模式中的模式密度问题的内容。)

    结束语

    从核心上看,设计模式实际是设计问题。因为程序员都很聪明,所以这些问题已经被解决了许多次。但也因为程序员的懒惰(用一种好的方式懒惰!),他们不愿意一次又一次地重复解决这些设计问题的工作。GoF 撰写的书籍(以及以前的模式工作)的贡献就是把为那些用今天的语言还不能更好表达的问题提供了通用的解决方案。这些麻烦的(如果是无价的)解决方案几乎可以解释为一种挑战,一种寻找更好(更易理解、更易重用、更易维护和更易组合)解决底层问题的方法的挑战。

    有些 OO 模式很难使用,因为它们解决的问题是横切的。对于这些问题,AOP 提供的解决方案正中问题核心。通过将横切行为搜集到单独的一个模块中,使行为变得更容易理解、修改和重用。因为这种类型的面向方面模式中的参与者不依赖模式代码,所以它们也变得更灵活、更易重用。在本文中,我指出了实际的示例场景和修改,从中可以看出,在用面向方面的方式解决模式问题时,要容易得多。

    新的解决方案采用了不同的形式。AOP 能在根本上把某些模式(例如修饰器)简化到很少观察到模式状态的程度。其他模式(例如观察者(Observer)模式)以可重用库模块的形式获得了新生命。总有一天您会发现,在扩展和实现模式方面,会自然得就像使用今天集合框架中的类一样。

    有没有特定于 AOP 的模式?

    值得进一步研究的一个迷人领域就是解决面向方面语言中重复出现问题的设计模式。从本文中您可以看到一点提示:两个方面采用消极初始化的 map,把状态松散地耦合到对象。除此之外,特定于 AOP 的模式(或候选模式)开始出现在用户社区中。Ramnivas Laddad 在他撰写的书籍 AspectJ 实战中(请参阅 参考资料)描述了几个这样的模式 —— 例如“工人对象”(Worker Object)和“ 虫子洞”(Wormhole)。Stefan Hanenberg 和 Arno Schmidmeier 在他们的论文“Idioms for Building Software Frameworks in AspectJ” 已经提出了几个有趣的候选模式,例如“模板通知”和“切入点方法”。只有时间会告诉我们这些正在出现的模式是否有用,是否是一些简单的术语,或者只是“处理 AspectJ 中破损事物的修理厂”。例如,在今年的 AOSD 会议上,我参与的一次会话讨论了对“消极初始化 map”这一概念提供更好的语言支持的可能性。对今天模式的研究,可能就会促进明天的语言特性的生成。

0
相关文章