观察者模式
定义:
观察者模式是使用频率最高的设计模式之一,又叫做发布-订阅(Publish/Subscribe)模式、模型-视图(Model/View)模式、源-收听者(Source/Listener)模式或者从属者(Dependents)模式
定义: 定义对象间的一种一对多的依赖关系,以便当一个对象的状态发生改变时,所有依赖于它的对象都得到通知并自动更新
类图:
-
Subject(目标):目标又称主题,指被观察的对象。
目标中定义了一个观察者的集合,一个目标可以被任意数量的观察者观察 目标提供对观察者注册和退订的维护 当目标的状态发生变化时,负责通知所有注册的观察者 目标类可以是接口、抽象类或具体类
- ConcreteSubject(具体目标):具体目标是目标类的子类或实现。通常它包含有经常发生改变的数据,当它的状态发生改变时,向它的各个观察者发出通知;同时它还实现了在目标类中定义的抽象业务逻辑方法(如果有的话)。
- Observer(观察者):观察者将对观察目标的改变做出反应,观察者一般定义为接口,该接口声明了更新数据的方法update(),可以在该方法中回调目标对象,以获取目标对象的数据
- ConcreteObserver(具体观察者):在具体观察者中维护一个指向具体目标对象的引用,它存储具体观察者的有关状态,这些状态需要和具体目标的状态保持一致;它实现了在抽象观察者Observer中定义的update()方法。
实现代码:
/* * 目标对象 - 它知道观察它的观察者,并提供注册、删除观察者的接口 */ abstract class Subject { //Vector ArrayList 一个线程安全一个效率高 protected Vector<Observer> observersVector = new Vector<>(); //注册观察者 public void attach(Observer observer){ observersVector.addElement(observer); } //删除观察者 public void detach(Observer observer){ observersVector.removeElement(observer); } //抽象通知方法 public abstract void notifyObservers(); }
/** * 观察者接口-定义一个更新接口给那些在目标发生改变时被通知的对象 */ public interface Observer { //更新 - 传入目标对象,方便获取相应的目标对象的状态 public void update(Subject subject); }
/** * 具体目标对象 - 负责把有关状态存入到相应观察者对象,并在自己状态发生改变时,通知各个观察者 */ public class ConcreteSubject implements Subject{ private String subjectState; public void setState(String subjectState) { this.subjectState = subjectState; notifyObservers(); } public String getState() { return subjectState; } @Override public void notifyObservers(){ Enumeration<?> enumeration =observersVector.elements(); while(enumeration.hasMoreElements()){ ((Observer)enumeration.nextElement()).update(this); } } }
/** * 具体观察者对象 - 实现更新方法,使自身状态与目标状态一致 */ public class ConcreteObserver implements Observer{ private String observerState; @Override public void update(Subject subject) { // 具体实现更新 observerState = ((ConcreteSubject)subject).getState(); } }
在上述实现代码中,具体观察者类ConcreteObserver的update()方法在执行时需要使用到具体目标类ConcreteSubject中的状态(属性),因此在ConcreteObserver与ConcreteSubject之间有时候还存在关联或依赖关系,在ConcreteObserver中update需要一个ConcreteSubject实例,通过该实例获取存储在ConcreteSubject中的状态。如果ConcreteObserver的update()方法不需要使用到ConcreteSubject中的状态属性,则可以对观察者模式的标准结构进行简化,在具体观察者ConcreteObserver和具体目标ConcreteSubject之间无须维持对象引用。如果在具体层具有关联关系,系统的扩展性将受到一定的影响,增加新的具体目标类有时候需要修改原有观察者的代码,在一定程度上违反了“开闭原则”,但是如果原有观察者类无须关联新增的具体目标,则系统扩展性不受影响
推模型和拉模型
观察者模式的实现中,分推模型与拉模型两种方式
推模型假定目标对象知道观察者需要的数据;而拉模型是目标对象不知道观察者具体需要什么数据,没办法的情况下,只能把自身传给观察者,让观察者自己按需取值。上面的示例代码就是典型的拉模型。推模型如何实现也很简单,如下:
比较:首先推模型可能使得观察者对象难以复用,因为观察者定义的update方法是按需定义的,意味着当出现新情况时需要提供新的update或重新实现观察者;而拉模式不会有上述问题,但却可能导致具体观察者和具体目标对象之间产生关联或依赖关系。一堆废话之后总结就是视情况而定。public void update(String subjectState){ //推模型 - 直接传入数据 observerState = subjectState; }
MVC的核心
为什么GOF四人组没有把MVC定义为一种设计模式,而是把它当做“一组用于构建用户界面的类集合”。在他们看来,MVC其实是其它三个经典的设计模式的演变:观察者模式(Observer)(Pub/Sub), 策略模式(Strategy)和组合模式(Composite)。MVC中models表示应用的数据,而views处理屏幕上展现给用户的内容。为此,MVC在核心通讯上基于推送/订阅模型。当一个model变化时它对应用其它模块发出更新通知(“publishes”),订阅者 (subscriber)——通常是一个Controller,然后更新对应的view。
Java中的观察者模式
概述
JAVA从AWT1.1开始的视窗系统的事件模型采用观察者模式,因此观察者模式在Java中的地位非常重要,在JDK的java.util包中,提供了Observable类以及Observer接口,它们构成了JDK对观察者模式的支持
(1) Observer接口:只定义了一个update()
(2) Observable类:被观察者都是Observable的子类。具体方法请参见Observable类源码
基于Java观察者模式的示例
//目标 class NewsPaper extends java.util.Observable{ private String content; //出版发行报纸 public void setContent(String content){ this.content = content; //Java-观察者模式必需 this.setChanged(); //推模型主动通知 this.notifyObservers(this.content); } public String getContent() { return content; } }
//观察者 class Reader implements java.util.Observer{ private String name; public Reader(String name){ this.name = name; } @Override public void update(Observable o, Object arg) { // 推模型 System.out.println(name+"收到报纸,内容:"+arg); // 拉模型 System.out.println(name+"收到报纸,内容:"+((NewsPaper)o).getContent()); } }
public class ObserverClient { public static void main(String[] args) { NewsPaper paper = new NewsPaper(); paper.addObserver(new Reader("cpp")); paper.addObserver(new Reader("hxq")); paper.setContent("你还好么"); } }
观察者模式总结
观察者模式的本质:触发联动
优点
- 观察者模式实现了观察者和目标之间的抽象耦合。被观察者所知道的只是一个具体观察者列表,且有一个共同的接口。由于观察者和被观察者并没有紧密的耦合,因此它们可以属于不同的抽象化层次
- 观察者模式实现了动态联动。通过注册的方式,在运行期间可以动态的管理注册的观察者,从而控制某个动作的联动范围,从而实现动态联动
- 观察者模式支持广播 通信
缺点
- 可能会引起无谓的操作。不管观察者需不需要,都会调用update方法
- 如果在被观察者之间有循环依赖,被观察者会触发进行循环调用,导致系统崩溃