观察者(Observer)模式的用途是定义对象之间的一对多依赖关系,因此,当一个对象的状态发生改变时,其所有依赖项都会得到通知,并自动更新。
这使得观察者适用于所有类型的通知需要
虽然实现的差异很明显,但在它们之间还是有一些相似之处。不论如何实现观察者,代码中都必须回答以下 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 语言能够更好地解决观察者角色带来的这些令人头痛的问题。
现在已经把类调整到它们在模式中的角色上了。但是,还需要回过头来,在对应的事件发生时触发通知。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
这使得观察者适用于所有类型的通知需要
虽然实现的差异很明显,但在它们之间还是有一些相似之处。不论如何实现观察者,代码中都必须回答以下 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 语言能够更好地解决观察者角色带来的这些令人头痛的问题。
现在已经把类调整到它们在模式中的角色上了。但是,还需要回过头来,在对应的事件发生时触发通知。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