观察者模式
模式定义
观察者模式是对象行为模式,又叫发布-订阅模式(publish/subscribe)模式,源-监视器模式(source/Listener)模式。
观察者模式定义了一种一对多的依赖关系,让多个观察者对象同事监听某一个主题对象,这个主题对象在状态上发生变化时,会通知所有观察者对象,使他们能够做出相应的操作。
模式结构
软件系统中,常常因为一个对象的状态发生改变时,某些其他对象做出相应的改变,做到这一点的设计方案有很多,但为了系统稳定性,选择低耦合的观察者模式,有助于系统的复用,保证高度协作。
模式描述
意图:定义对象间的一种一对多的依赖关系,当一个对象的状态发生改变时,所有依赖于它的对象都得到通知并被自动更新。
主要解决:一个对象状态改变给其他对象通知的问题,而且要考虑到易用和低耦合,保证高度的协作。
何时使用:一个对象(目标对象)的状态发生改变,所有的依赖对象(观察者对象)都将得到通知,进行广播通知。
如何解决:使用面向对象技术,可以将这种依赖关系弱化。
关键代码:在抽象类里有一个 ArrayList 存放观察者们。
使用场景:
- 一个抽象模型有两个方面,其中一个方面依赖于另一个方面。将这些方面封装在独立的对象中使它们可以各自独立地改变和复用。
- 一个对象的改变将导致其他一个或多个对象也发生改变,而不知道具体有多少对象将发生改变,可以降低对象之间的耦合度。
- 一个对象必须通知其他对象,而并不知道这些对象是谁。
- 需要在系统中创建一个触发链,A对象的行为将影响B对象,B对象的行为将影响C对象……,可以使用观察者模式创建一种链式触发机制。
角色
- 抽象主题(Subject)角色:抽象主题角色把所有对观察者对象的引用保存在一个聚集(比如ArrayList)里,每一个主题都可以有任何数量的观察者。抽象主题提供一个接口(抽象类),可以增加和删除观察者,抽象主题角色又叫抽象被观察者(Observable)角色。
- 具体主题(ConcreteSubject)角色:将有关状态存入具体的观察者对象;在具体主题的内部状态改变时,给所有登录订阅过的观察者发送通知。具体主题对象又叫具体被观察者(Concrete Observable)角色。
- 抽象观察者(Observer)角色:给所有的具体观察者定义一个接口,在得到主题的通知时更新自己,这个接口叫做更新接口。
- 具体观察者(Concrete Observer)角色:存储与主题的状态自恰的状态,具体观察者实现抽象观察者角色所要求的更新接口,以便使本身的状态与主题相协调,具体观察者可以保持一个指向具体主题对象的引用。
实现代码示例:
抽象主题类
public abstract class Subject { /** * 用来保存注册的观察者对象 */ private List<Observer> list = new ArrayList<Observer>(); /** * 注册观察者对象 * @param observer 观察者对象 */ public void attach(Observer observer){ list.add(observer); System.out.println("Attached an observer"); } /** * 删除观察者对象 * @param observer 观察者对象 */ public void detach(Observer observer){ list.remove(observer); } /** * 通知所有注册的观察者对象 */ public void nodifyObservers(String newState){ for(Observer observer : list){ //推模式下,需要传入状态 //拉模式下,不需要传入状态 observer.update(newState); } } }
具体主题角色类:
public class ConcreteSubject extends Subject{ private String state; public String getState() { return state; } public void change(String newState){ state = newState; System.out.println("主题状态为:" + state); //状态发生改变,通知各个观察者 //在推模式下,需要传入参数 //拉模式下,不需要传入参数 this.nodifyObservers(state);//调用父类定义方法 } }
抽象观察者角色类:
public interface Observer { /** * 更新接口 * @param state 更新的状态 */ public void update(String state); }
具体观察者角色类:
public class ConcreteObserver implements Observer { //观察者的状态 private String observerState; @Override public void update(String state) { /** * 更新观察者的状态,使其与目标的状态保持一致 */ observerState = state; System.out.println("状态为:"+observerState); } }
客户端:
public class Client { public static void main(String[] args) { //创建主题对象 ConcreteSubject subject = new ConcreteSubject(); //创建观察者对象 Observer observer = new ConcreteObserver(); //将观察者对象登记到主题对象上 subject.attach(observer); //改变主题对象的状态 subject.change("new state"); } }
推模式和拉模式
推模式
上面的例子就是典型的推模式,主题对象向观察者推送详细信息。
拉模式
主题对象在通知观察者的时候,只传递少量信息。如果观察者需要更具体的信息,由观察者主动到主题对象中获取,相当于是观察者从主题对象中拉数据。一般这种模型的实现中,会把主题对象自身通过update()方法传递给观察者,这样在观察者需要获取数据的时候,就可以通过这个引用来获取了。
调用观察者的方法时,不需要传入参数
观察者模式与订阅模式
观察者注册到目标;目标发生改变时,调用观察者方法
订阅者把自己想订阅的事件注册到调度中心,当该事件触发时候,发布者发布该事件到调度中心(顺带上下文),由调度中心统一调度订阅者注册到调度中心的处理代码
总结
- 从两张图片可以看到,最大的区别是调度的地方。
虽然两种模式都存在订阅者和发布者(具体观察者可认为是订阅者、具体目标可认为是发布者),但是观察者模式是由具体目标调度的,而发布/订阅模式是统一由调度中心调的,所以观察者模式的订阅者与发布者之间是存在依赖的,而发布/订阅模式则不会。 - 两种模式都可以用于松散耦合,改进代码管理和潜在的复用。