交通信号灯相信大家都不陌生,红灯停、绿灯行,见了黄灯等一等。交通信号灯、海边的信号塔等等,都伫立在那里,随时准备给行人/货船发送信号,以提醒行人/货船该采取相应行动了。信号灯就是发布信号的对象,行人是接收信号并采取具体行动的观察者。这一过程,在程序设计中也常常用到,即本文介绍的观察者模式。
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 class Observer { protected Subject subject; public abstract void update(); } class Subject { private List<Observer> observers = new ArrayList<>(); private int state; public void add(Observer o) { observers.add(o); } public int getState() { return state; } public void setState(int value) { this.state = value; execute(); } private void execute() { for (Observer observer : observers) { observer.update(); } } } class HexObserver extends Observer { public HexObserver(Subject subject) { this.subject = subject; this.subject.add(this); } public void update() { System.out.print(" " + Integer.toHexString(subject.getState())); } } class OctObserver extends Observer { public OctObserver(Subject subject) { this.subject = subject; this.subject.add( this ); } public void update() { System.out.print(" " + Integer.toOctalString(subject.getState())); } } class BinObserver extends Observer { public BinObserver(Subject subject) { this.subject = subject; this.subject.add(this); } public void update() { System.out.print(" " + Integer.toBinaryString(subject.getState())); } } public class ObserverDemo { public static void main( String[] args ) { Subject sub = new Subject(); // Client configures the number and type of Observers new HexObserver(sub); new OctObserver(sub); new BinObserver(sub); Scanner scan = new Scanner(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操作。
/** * 事件源 */ class EventSource { public interface Observer { void update(String event); } private final List<Observer> observers = new ArrayList<>(); public void addObserver(Observer observer) { observers.add(observer); } public void scanSystemIn() { Scanner scanner = new Scanner(System.in); while (scanner.hasNextLine()) { String line = scanner.nextLine(); notifyObservers(line); } } private void notifyObservers(String event) { observers.forEach(observer -> observer.update(event)); } }
调用
public class ObserverDemo { public static void main(String[] args) { EventSource eventSource = new EventSource(); 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来避免内存占用过高。