技术开发 频道

用AspectJ增强设计模式下

    Java 语言观察者的分析

    从实现的步骤中,您可能感觉到,观察者(Observer)模式是一个重量级的模式。这里的分析将探索该模式对系统当前和潜在的冲击:

    横切:在音乐商店应用程序中,记费的观察关注点(observation concern)既包含 记帐人员(Song 和 Playlist),又包含 开票人员(BillingService)。Java 语言实现还附加了胶水代码的位置。因为它影响三个不同的类,这些类的目标不仅仅是这个观察关系,所以记费观察就代表了一个横切关注点。 

    易于理解:先从领域对象的视角开始查看系统。观察者的 OO 实现要求对受模式影响的领域类进行修改。正如在实现类中看到的,这些修改的工作量并不小。对于 Subject 来说,需要给每个类添加多个方法,才能让它在模式中发挥作用。如果领域概念(播放列表、歌曲等)的简单抽象被观察者(Observer)模式的机制阻塞,又会怎么样呢?(请参阅“有没有更好的 OO 观察者?”,了解为什么有些非 Java 语言可能让这项工作更容易。)

    也可以从模式的视角研究系统。在 Java 语言实现中,模式冒着“消失在代码中”的风险。开发人员必须检查模式的三个方面( Observer、Subject 和连接两者的客户代码),才能有意识地构建出模式的坚固模型。如果发现模式的一部分,聪明的开发人员会寻找其他部分,但是没有可以把这些部分集中在一起是“BillingObserver”模块。 

    重用:要重用这个模式,必须从头开始重新实现它。通常,可以利用预先构建好的支持类(例如,java.util 包含 Observer 和 Observable )填充模式的某些部分,但是大部分工作仍然需要自己来做。

    维护:为了帮助您思考观察者(Observer)模式的维护成本,要先考虑避免双重计费的需求。如果看一眼模式第一个实现的输出(请参阅 清单 2),就会看到应用程序对播放列表上下文中播放歌曲的计费。这并不令人感到惊讶,因为这就是编写这段代码要做的工作。但是,市场部门想为播放列表提供一个总体报价,以鼓励批量购买。换句话说,他们想对播放列表计费,而不是对播放列表中的歌曲计费。

    很容易就可以想像得到,这会给传统的观察者实现带来麻烦。需要修改每个 play() 方法,让它们接受一个参数,表明它是由另一个 play() 方法调用的。或者,可以维护一个 ThreadLocal,用它来跟踪这方面的信息。不管属于哪种情况,都只在调用上下文环境得到保证的情况下才调用 notifyObservers()。这些修改可能会给已经饱受围困的模式参与者再添加了一些负担和复杂性。因为变化将影响多个文件,所以在重新设计的过程中就可能出现 bug。

    组合:需要考虑的另一个场景就是不同观察者的观察。从设置中您可能会想起,音乐服务应当跟踪最流行的歌曲。但是统计搜集只被应用于歌曲播放,而不涉及歌词查看或播放列表播放。这意味着 SongCountObserver 观察的操作集合与记费观察者观察的略有不同。

    要实现这点,就不得不修改 Song,让它维护一个独立的 Observer 列表,只对 play() 通知感兴趣(对歌词操作没有兴趣)。然后,play() 方法会独立于记费事件触发这个事件。这样 OO 模式就保证了具体的 Subject 对具体的 Observer 的直接依赖。但是这种情况看起来是不可能的,因为 Song 必须为每种类型的 Observer 触发不同的事件。面对着另一种 Observer 类型,Java 语言的实现就会崩溃。
正如您可能想到的,AspectJ 观察者为这个分析所提出的场景提供了圆满的解决方案。在研究它们之前,我将介绍 AspectJ 如何处理基本模式。

    AspectJ 观察者

    就像使用我在这篇文章中考虑的其他模式一样,观察者(Observer)模式的目的,甚至是观察者(Observer)模式的基本结构,在用 AspectJ 实现时保持不变。但是,有一个关键的区别。通过使用面向方面的继承,在对模式进行定制,从而满足个人需求时,可以重用模式中对于所有实现都公用的那些部分。请记住,观察者(Observer)模式实现必须回答以下 4 个问题:

    1.哪个对象是主体,哪个对象是观察者?
    2.什么时候主体应当向它的观察者发送通知?
    3.当接收到通知时,观察者应该做什么?
    4.观察关系应当在什么时候开始,什么时候终止? 

    抽象的模式方面

    首先我要回顾抽象方面的工作方式。与抽象类非常相似,在使用抽象方面之前,必须扩展它。抽象方面可以定义抽象的切入点(切入点有名称,但是没有主体),然后定义这些切入点上的具体的通知(advice)。这意味着由父方面指定行为,子方面控制在哪里应用行为。另外,抽象方面还能定义子方面必须覆盖的抽象方法,就像抽象类能够做到的那样。记住这些事实,现在来看一下来自 设计模式项目 的 ObserverProtocol 方面。

    角色定义

    就像使用其他实现一样,角色由标记器接口指定。但是,在 AspectJ 实现的情况下,接口是空的(可能是被 AspectJ 5 中的注释代替)。接口以 ObserverProtocol 成员的形式出现:

/** * This interface is used by extending aspects to say what types * can be Subjects. */ public interface Subject{} /** * This interface is used by extending aspects to say what types * can be Observers. */ public interface Observer{}

    AOP 设计模式项目

    我对设计模式和 AspectJ 的狂热开始于不列颠哥伦比亚大学赞助的面向方面设计模式实现项目(请参阅 参考资料)。正如我在 这篇文章的第一部分 提到的,Jan Hanneman 和 Gregor Kiczales 调查了 23 个 GoF 模式,并用 Java 语言和 AspectJ 为每一种模式创建了参考实现。结果非常迷人,值得研究,在原始的模式中,有 17 个模式在使用 AOP 实现时得到了改进。最令人兴奋的是模式库。项目的作者能够从 13 个模式中提取出可重用的抽象方面,并在 Mozilla 公共许可下免费提供它们。本文介绍的 ObserverProtocol 方面就取自该项目。请参阅 参考资料 一节,以下载这些模式。

    跟踪观察者

    方面没有强迫参与者跟踪观察者,而是把这个职责集中起来。清单 3 包含的代码实现了模式的这一部分。从 第 1 部分 的修饰器 AOP 实现中,您应当了解了这个通用术语。同样,方面使用消极初始化的 Map 对特定于对象的状态进行跟踪。(同样,这个模式也有包含初始化模型的替代品,但是它们超出了本文的范畴。)请注意清单 3 中的一个有趣的元素 —— addObserver 和 removeObserver 方法是公共的。这意味着系统中任何地方的代码都可以用编程的方式决定是否参与到这个模式中。

    清单 3. ObserverProtocol 管理观察主角的观察者

/** * Stores the mapping between Subjects and * Observers. For each Subject, a LinkedList * is of its Observers is stored. */ private WeakHashMap perSubjectObservers; /** * Returns a Collection of the Observers of * a particular subject. Used internally. */ protected List getObservers(Subject subject) { if (perSubjectObservers == null) { perSubjectObservers = new WeakHashMap(); } List observers = (List)perSubjectObservers.get(subject); if ( observers == null ) { observers = new LinkedList(); perSubjectObservers.put(subject, observers); } return observers; } /** * Adds an Observer to a Subject. */ public void addObserver(Subject subject, Observer observer) { getObservers(subject).add(observer); } /** * Removes an observer from a Subject. */ public void removeObserver(Subject subject, Observer observer) { getObservers(subject).remove(observer); } //aspect continues...

 

0
相关文章