介绍
意图:
定义对象间的一种一对多的依赖关系,当一个对象的状态发生改变时,所有依赖于它的对象都得到通知并被自动更新。
主要解决:
一个对象状态改变给其他对象通知的问题,而且要考虑到易用和低耦合,保证高度的协作。
何时使用:
一个对象(目标对象)的状态发生改变,所有的依赖对象(观察者对象)都将得到通知,进行广播通知。
如何解决:
使用面向对象技术,可以将这种依赖关系弱化。
关键代码:
在抽象类里有一个 ArrayList 存放观察者们。
应用实例:
- 数据发生变化时,所有关于这个数据的图表受到通知,进行变化
- 天气预报,通知大家天气变化,大家对天气做准备
优点:
- 观察者和目标是抽象耦合的,它们属于一个系统中的不同抽象层次,目标处于较低层次,通知处于较高层次观察者,自己发生了变化。
- 建立一套触发机制。
缺点:
- 如果一个目标对象有很多的直接和间接的观察者的话,将所有的观察者都通知到会花费很多时间。
- 如果在观察者和观察目标之间有循环依赖的话,观察目标会触发它们之间进行循环调用,可能导致系统崩溃。
- 观察者模式没有相应的机制让观察者知道所观察的目标对象是怎么发生变化的,而仅仅只是知道观察目标发生了变化。
使用场景:
- 一个抽象模型有两个方面,其中一个方面依赖于另一个方面。将这些方面封装在独立的对象中使它 们可以各自独立地改变和复用。
- 一个对象的改变将导致其他一个或多个对象也发生改变,而不知道具体有多少对象将发生改变,可以降低对象之间的耦合度。
- 一个对象必须通知其他对象,而并不知道这些对象是谁。需要在系统中创建一个触发链,A对象的行为将影响B对象,B对象的行为将影响C对象……,可以使用观察者模式创建一种链式触发机制。
注意事项:
-
JAVA 中已经有了对观察者模式的支持类。
-
避免循环引用。
-
如果顺序执行,某一观察者错误会导致系统卡壳,一般采用异步方式。
-
当一个观察者依赖于多个目标时,必须扩展update接口使观察者知道是哪一个目标送来的通知,简单的方法就是把目标当成update的一个参数。
-
由谁来触发更新:
- 由目标对象的状态设定操作在改变目标对象的状态后自动调用notify。优点是客户不需要记住要在目标上调用notify,缺点是多个连续的操作会产生多次连续的更新,可能效率较低。
- 让客户端负责在适当的时候调用notify。优点是客户可以在一系列的状态改变完成后再一次性地触发更新,避免不必要的中间更新。缺点是客户增加了触发更新的责任。
-
在发出通知前确保状态自身一致,因为观察者在更新其状态的过程中需要查询目标的当前状态。
-
当目标和观察者之间的依赖关系特别负责的时候,可能需要维护一个更改管理器,其目的是尽量减少观察者反映其目标的状态编发所需的工作量。它有三个责任:
- 它将一个目标映射到他的观察者并提供一个接口来维护这个映射。这就不需要由目标来维护对其观察者的引用,反之亦然。
- 它定义一个特定的更新策略。
- 根据一个目标的请求,它更新所有依赖于这个目标的观察者。
-
在观察者模式的实现上,有推模式和拉模式两种方式:
- 推模式
Subject主动向Observer推送消息,不管对方是否需要,推送的信息通常是目标对象的全部或部分数据,相当于广播通信。 - 拉模型
Subject在通知Observer时只传递少量信息,如果观察者需要更具体的信息,再由Observer主动去拉取数据。这样的模型实现中会把Subject自身通过update方法传入到Observer。
推模型是假定主题对象知道观察者需要的数据;而拉模型是主题对象不知道观察者具体需要什么数据,没有办法的情况下,干脆把自身传递给观察者,让观察者自己去按需要取值。
推模型可能会使得观察者对象难以复用,因为观察者的update()方法是按需要定义的参数,可能无法兼顾没有考虑到的使用情况。这就意味着出现新情况的时候,就可能提供新的update()方法,或者是干脆重新实现观察者;而拉模型就不会造成这样的情况,因为拉模型下,update()方法的参数是主题对象本身,这基本上是主题对象能传递的最大数据集合了,基本上可以适应各种情况的需要。 - 推模式
UML
抽象目标(Subject):该角色是一个抽象类或接口,定义了增加、删除、通知观察者对象的方法。
具体目标(ConcreteSubject):该角色继承或实现了抽象主题,定义了一个集合存入注册过的具体观察者对象,在具体主题的内部状态发生改变时,给所有注册过的观察者发送通知。
抽象观察者(Observer):该角色是具体观察者的抽象类,定义了一个更新方法。
具体观察者(ConcrereObserver):该角色是具体的观察者对象,在得到具体主题更改通知时更新自身的状态。
实现
推模式
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 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(){
for(Observer observer : list){
observer.update(this);
}
}
}
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();
}
}
public interface Observer {
/**
* 更新接口
* @param subject 传入主题对象,方面获取相应的主题对象的状态
*/
public void update(Subject subject);
}
public class ConcreteObserver implements Observer {
//观察者的状态
private String observerState;
@Override
public void update(Subject subject) {
/**
* 更新观察者的状态,使其与目标的状态保持一致
*/
observerState = ((ConcreteSubject)subject).getState();
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");
}
}
运行结果
主题状态为:new state
观察者状态为:new state