用AspectJ增强设计模式下
Java 语言的观察者
虽然实现的差异很明显,但在它们之间还是有一些相似之处。不论如何实现观察者,代码中都必须回答以下 4 个问题:
1.哪个对象是主体,哪个对象是观察者?
2.什么时候主体应当向它的观察者发送通知?
3.当接收到通知时,观察者应该做什么?
4.观察关系应当在什么时候开始,什么时候终止?
我将用这些问题作为框架,带您经历观察者(Observer)模式的 OO 实现。
角色定义
首先从标记器接口来分配角色开始。Observer 接口只定义了一个方法:update(),它对应着 Subject 发送通知时执行的操作。 Subject 承担着更多的职责。它的标记器接口定义了两个方法,一个用来跟踪观察者,另一个用来通知事件的那些观察者。
public interface Subject { public void addObserver(Observer o); public void removeObserver(Observer o); public void notifyObservers(); }
一旦定义了这些角色,就可以把它们应用到系统中对应的角色上。
应用观察者角色
可以修改 BillingService,用以下少量代码实现观察者接口:
public class BillingService implements Observer { //... public void update(Subject subject) { generateChargeFor((Playable) subject); } }
跟踪和通知观察者
一旦这项工作完成,就可以转移到两个 Subject。在这里,要对 Song 进行修改:
private Set observers = new HashSet(); public void addObserver(Observer o) { observers.add(o); } public void removeObserver(Observer o) { observers.remove(o); } public void notifyObservers() { for (Observer o : observers) { o.update(this); } }
现在面临的是一个不太让人高兴的任务:必须把 Subject 的这个实现剪切、粘贴到 Playlist 中。将 Subject 实现的一部分摘录到一个助手类中,这有助于缓解设计的弊端,但是仍然不足以完全消除它。
有没有更好的 OO 观察者?
这篇文章侧重在 Java 语言的 OO 模式实现上。读者可能想知道是否有特性更丰富的 OO 语言能够更好地解决观察者角色带来的这些令人头痛的问题。给这篇文章带来灵感的 .NET 实现采用委托和事件,极大地减轻了 .NET 版本的 Song 和 Playlist 类的负担。(要比较两个实现,请参阅 参考资料 一节。)类似的,支持多继承的 OO 语言也能把 Song 的负担限制到在一个 Subject 支持类中的混合上。每种技术都有很大帮助,但是都不能处理触发通知或组合多个模式实例的问题。我将在本文的后面部分讨论这些问题的后果。
触发事件
现在已经把类调整到它们在模式中的角色上了。但是,还需要回过头来,在对应的事件发生时触发通知。Song 要求两个附加通知,而 Playlist 则需要一个:
//...in Song public void play() { System.out.println("Playing song " + getName()); notifyObservers(); } public void showLyrics(){ System.out.println("Displaying lyrics for " + getName()); notifyObservers(); } //...in Playlist public void play() { System.out.println("playing album " + getName()); for (Song song : songs) { //... } notifyObservers(); }
需要牢记的是,虽然示例系统只需要一个状态改变通知,但是实际的系统可能许多许多通知。例如,我曾经开发过一个应用程序,该应用程序要求在 更改购物车状态时发送观察者风格的通知。为了做到这一点,我在购物车和相关对象中的 10 个以上的位置应用了通知逻辑。
随着各个角色准备好参与到模式中,剩下来要做的就是把它们连接起来。
启动观察关系
为了让 BillingService 开始观察 Song 或 Playlist,需要添加胶水代码,由它调用 song.addObserver(billingService)。这个要求与 第 1 部分 中描述的适配器和修饰器的胶水代码的要求类似。除了影响参与者之外,模式还要求对系统中不确定的部分进行修改,以便激活模式。清单 2 包含的代码模拟了客户与系统的交互,并整合了这个胶水代码,胶水代码是突出显示的。清单 2 还显示了示例系统的输出。
清单 2. 连接主体和观察者(并试验系统)的客户代码
public class ObserverClient { public static void main(String[] args) { BillingService basicBilling = new BillingService(); BillingService premiumBilling = new BillingService(); Song song = new Song("Kris Kringle Was A Cat Thief"); song.addObserver(basicBilling); Song song2 = new Song("Rock n Roll McDonald's"); song2.addObserver(basicBilling); //this song is billed by two services, //perhaps the label demands an premium for online access? song2.addObserver(premiumBilling); Playlist favorites = new Playlist("Wesley Willis - Greatest Hits"); favorites.addObserver(basicBilling); List songs = new ArrayList(); songs.add(song); songs.add(song2); favorites.setSongs(songs); favorites.play(); song.showLyrics(); } } //Output:
playing playlist Favorites
Playing song Kris Kringle Was A Cat Thief
generating charge for : Kris Kringle Was A Cat Thief
Playing song Rock n Roll McDonald's
generating charge for : Rock n Roll McDonald's
generating charge for : Rock n Roll McDonald's
generating charge for : Favorites
Displaying lyrics for Kris Kringle Was A Cat Thief
generating charge for : Kris Kringle Was A Cat Thief