《图解设计模式》笔记(八)管理状态
十七、Observer模式:发送状态变化通知
Observer :“进行观察的人”,也就是“观察者”。
在 Observer模式中,当观察对象的状态发生变化时,会通知给观察者。
适用场景:根据对象状态进行相应处理.
示例程序:观察者将观察一个会生成数值的对象,并将它生成的数值结果显示出来。不同的观察者的显示方式不一样:DigitObserver以数字形式显示数值,GraphObserver以简单的图示形式显示数值。
示例程序类图
Observer
public interface Observer {
public abstract void update(NumberGenerator generator);
}
NumberGenerator
import java.util.ArrayList;
import java.util.Iterator;
public abstract class NumberGenerator {
private ArrayList observers = new ArrayList(); // 保存Observer们
public void addObserver(Observer observer) {
// 注册Observer
observers.add(observer);
}
public void deleteObserver(Observer observer) {
// 删除Observer
observers.remove(observer);
}
public void notifyObservers() {
// 向Observer发送通知
Iterator it = observers.iterator();
while (it.hasNext()) {
Observer o = (Observer)it.next();
o.update(this);
}
}
public abstract int getNumber(); // 获取数值
public abstract void execute(); // 生成数值
}
RandomNumberGenerator
import java.util.Random;
public class RandomNumberGenerator extends NumberGenerator {
private Random random = new Random(); // 随机数生成器
private int number; // 当前数值
public int getNumber() {
// 获取当前数值
return number;
}
public void execute() {
for (int i = 0; i < 20; i++) {
number = random.nextInt(50);
notifyObservers();
}
}
}
DigitObserver
public class DigitObserver implements Observer {
// 接收参数为NumberGenerator的实例,然后通过调用NumberGenerator类的实例的getNumber方法可以获取到当前的数值,并将这个数值显示出来。
public void update(NumberGenerator generator) {
System.out.println("DigitObserver:" + generator.getNumber());
try {
// 为能够看清它是如何显示数值的,使用Thread.sleep来降低程序的运行速度。
Thread.sleep(100);
} catch (InterruptedException e) {
}
}
}
GraphObserver
public class GraphObserver implements Observer {
public void update(NumberGenerator generator) {
System.out.print("GraphObserver:");
int count = generator.getNumber();
for (int i = 0; i < count; i++) {
System.out.print("*");
}
System.out.println("");
try {
Thread.sleep(100);
} catch (InterruptedException e) {
}
}
}
Main
public class Main {
public static void main(String[] args) {
NumberGenerator generator = new RandomNumberGenerator();
Observer observer1 = new DigitObserver();
Observer observer2 = new GraphObserver();
generator.addObserver(observer1);
generator.addObserver(observer2);
generator.execute();
}
}
角色
- Subject(观察对象)
定义了注册观察者和删除观察者的方法。
声明了“获取现在的状态”的方法。
示例中是NumberGenerator类。 - ConcreteSubject(具体的观察对象)
当自身状态发生变化后,它会通知所有已经注册的Observer角色。
示例中是RandomNumberGenerator类。 - Observer(观察者)
接收来自Subject角色的状态变化的通知。为此,它声明了update方法。
示例中是Observer接口。 - ConcreteObserver (具体的观察者)
当它的update方法被调用后,会去获取要观察的对象的最新状态。
示例中是DigitObserver类和GraphObserver类。
拓展思路的要点
这里也出现了可替换性
使用设计模式的目的之一就是使类成为可复用的组件。
在本模式中,有带状态的ConcreteSubject角色和接收状态变化通知的ConcreteObserver角色。
连接这两个角色的就是它们的接口(API) Subject角色和Observer角色。
一方面RandomNumberGenerator类并不知道,也无需在意正在观察自己的(自己需要通知的对象)到底是DigitObserver类的实例还是GraphObserver类的实例。不过它知道在它的observers字段中所保存的观察者们都实现了Observer接口。因为这些实例都是通过addObserver方法注册的,这就确保了它们一定都实现了Observer接口,一定可以调用它们的update方法。
另一方面,DigitObserver类也无需在意自己正在观察的究竟是RandomNumberGenerator类的实例还是其他XXXXNumberGenerator类的实例。不过,DigitObserver类知道它们是NumberGenerator类的子类的实例,并持有getNumber方法。
利用抽象类和接口从具体类中抽出抽象方法
在将实例作为参数传递至类中,或者在类的字段中保存实例时,不使用具体类型,而是使用抽象类型和接口
这样的实现方式可以轻松替换具体类。
Observer的顺序
在示例的notifyObservers方法中,先注册的Observer的update方法会先被调用。
通常在设计时就要注意调用顺序。
在示例中,绝不能因为先调用 DigitObserver的update方法后调用 GraphObserver的update方法而导致应用程序不能正常工作。
通常,只要保持各个类的独立性,就不会有上面这种类的依赖关系混乱问题。
不过,还需要注意下面将要提到的情况。
当Observer的行为会对Subject产生影响时
在示例中,RandomNumberGenerator类会在自身内部生成数值,调用 update方法。
但在通常的 Observer模式中,也可能是其他类触发Subject角色调用update方法。
例如,在GUI应用程序中,多数情况下是用户按下按钮后会触发update方法被调用。
Observer角色也有可能会触发 Subject 角色调用 update方法。这时注意不要导致方法被循环调用。
Subject 状态发生变化
↓
通知Observer
↓
Observer调用 Subject 的方法
↓
导致Subject状态发生变化
↓
通知Observer
↓
……
传递更新信息的方式
传递给u