交通信号灯相信大家都不陌生,红灯停、绿灯行,见了黄灯等一等。交通信号灯、海边的信号塔等等,都伫立在那里,随时准备给行人/货船发送信号,以提醒行人/货船该采取相应行动了。信号灯就是发布信号的对象,行人是接收信号并采取具体行动的观察者。这一过程,在程序设计中也常常用到,即本文介绍的观察者模式。
1.观察者模式
观察者模式(Observer Pattern)定义了一种一对多的依赖关系,让多个观察者对象同时监听某一个主题对象。这个主题对象在状态发生变化时,会通知所有观察者对象,使它们能够自动更新自己。
在事件驱动的GUI开发中观察者模式十分常用。另外,著名的MVC软件设计思想也应用了观察者模式:
Model 业务模型,相当于观察者模式中的主题对象
View 视图层,相当于观察者
Controller 控制器,协调M和V
当Model数据发生变化时,会通知View更新视图。所以,观察者模式又叫做模型-视图模式,或者发布-订阅模式。
2. 代码实现
2.1 紧耦合形式
Observer抽象类,有三个子类:HexObserver十六进制, OctObserver八进制,BinObserver二进制,注意观察着Observer持有主题Subject的引用。
Subject主题对象持有Observer的引用,并提供添加add观察者的方法, 当Subject状态改变时setState, 会调用execute方法,execute方法中遍历所有观察者对象并调用每个观察者对象的update方法更新,观察者对象的update方法中,通过回调主题对象的getState()方法获取主题的状态。
这种方式Subject持有Observer引用, Observer也持有Subject的引用,就是紧耦合的实现方式。
abstract classObserver {protectedSubject subject;public abstract voidupdate();
}classSubject {private List observers = new ArrayList<>();private intstate;public voidadd(Observer o) {
observers.add(o);
}public intgetState() {returnstate;
}public void setState(intvalue) {this.state =value;
execute();
}private voidexecute() {for(Observer observer : observers) {
observer.update();
}
}
}class HexObserver extendsObserver {publicHexObserver(Subject subject) {this.subject =subject;this.subject.add(this);
}public voidupdate() {
System.out.print(" " +Integer.toHexString(subject.getState()));
}
}class OctObserver extendsObserver {publicOctObserver(Subject subject) {this.subject =subject;this.subject.add( this);
}public voidupdate() {
System.out.print(" " +Integer.toOctalString(subject.getState()));
}
}class BinObserver extendsObserver {publicBinObserver(Subject subject) {this.subject =subject;this.subject.add(this);
}public voidupdate() {
System.out.print(" " +Integer.toBinaryString(subject.getState()));
}
}public classObserverDemo {public static voidmain( String[] args ) {
Subject sub= newSubject();//Client configures the number and type of Observers
newHexObserver(sub);newOctObserver(sub);newBinObserver(sub);
Scanner scan= newScanner(System.in);for (int i = 0; i < 5; i++) {
System.out.print("\nEnter a number: ");
sub.setState(scan.nextInt());
}
}
}
2.2 松耦合形式
以下代码基于jdk1.8以上,定义内部类,可以避免在Observer中定义Subject的引用。
EventSource事件源, scanSystemIn()方法监听控制台输入,当控制台有字符串输入时,EventSource会通知Observer进行update操作。
/*** 事件源*/
classEventSource {public interfaceObserver {voidupdate(String event);
}private final List observers = new ArrayList<>();public voidaddObserver(Observer observer) {
observers.add(observer);
}public voidscanSystemIn() {
Scanner scanner= newScanner(System.in);while(scanner.hasNextLine()) {
String line=scanner.nextLine();
notifyObservers(line);
}
}private voidnotifyObservers(String event) {
observers.forEach(observer->observer.update(event));
}
}
调用
public classObserverDemo {public static voidmain(String[] args) {
EventSource eventSource= newEventSource();for (int i = 0; i < 3; i++) {
eventSource.addObserver(event->{
System.out.println("Received response: " +event);
});
}
eventSource.scanSystemIn();
}
}
控制台输入"click",然后事件源监听到,通知Observer,Observer将消息打印出来:
click
Received response: click
Received response: click
Received response: click
3.总结
其实为了避免紧耦合,实际开发中往往在Subject和Observer之间加一个中间对象,这个中间对像本质上就是一个队列,当Subject状态发生变化时,这个变化信息传到中间对象队列中,由中间对象对这条消息进行处理,包装成Observer统一的一种接收形式,然后根据某种策略发给Observer。消息中间件就是这样一种实现,
另外,注意主题对象由于持有观察着对象集合,如果观察者数量庞大时,要注意内存的占用,避免发生内存泄漏,通常,可以考虑弱引用weak reference来避免内存占用过高。