定义:
定义了对象之间的一种一对多的依赖关系,当一个对象状态发生改变时,它的所有依赖者都会收到通知并自动更新。
观察者模式也叫发布订阅模式(Publish/subscribe), 类图如下:
观察者模式中的角色
- 主题角色,主题角色就是被观察者,它可以注册或者删除观察者对象,并且持有一个观察者对象的集合,当状态发生变化时,主题可以通知所有注册的观察集合中的每一个观察者进行更新。
- 观察者角色,观察者角色可以被注册到主题当中,它拥有一个更新的方法,当主题发生变化时,它的更新方法会被调用。
相关代码:
public interface Subject {
public void registerObserver(Observer o);
public void removeObserver(Observer o);
public void notifyObservers();
}
public class ConcreteSubject implements Subject {
private String mState;
private List<Observer> mObserverList = new ArrayList<>();
@Override
public void registerObserver(Observer o) {
mObserverList.add(o);
System.out.println("主题注册一个观察者");
}
@Override
public void removeObserver(Observer o) {
if (mObserverList.contains(o)) {
mObserverList.remove(o);
}
System.out.println("主题删除一个观察者");
}
@Override
public void notifyObservers() {
System.out.println("主题通知所有观察者更新...");
for (Observer observer : mObserverList) {
observer.update(mState);
}
}
public void changeState(String state) {
this.mState = state;
System.out.println("主题状态发生变化:"+state);
notifyObservers();
}
public String getState() {
return mState;
}
}
public interface Observer {
public void update(String state);
}
public class ConcreteObserver implements Observer {
@Override
public void update(String state) {
System.out.println("观察者收到来自主题的状态:"+state);
}
}
public class Client {
public static void main(String[] args) {
//创建一个主题对象
ConcreteSubject subject = new ConcreteSubject();
//创建一个观察者对象
Observer observer = new ConcreteObserver();
//为主题对象注册一个观察者
subject.registerObserver(observer);
//改变主题状态
subject.changeState("a new state");
}
}
这里的示例代码只为主题对象添加了一个观察者,实际可以添加任意数量的观察者。
输出结果:
观察者与被观察者之间的数据传递有两种方式:
- 被观察者向观察者推送数据,主题对象向观察者推送主题的详细信息,不管观察者是否需要,推送的信息通常是主题对象的全部或部分数据。
- 观察者从被观察者拉取数据,主题对象在通知观察者的时候,将自身对象通过update方法传递给观察者,观察者通过主题对象引用主动从主题对象中get需要的数据。
前面的示例代码就是推送数据的方式,下面给出拉取数据方式的示例代码:
public class ConcreteSubject implements Subject {
//....
//其它代码不变
//....
@Override
public void notifyObservers() {
System.out.println("主题通知所有观察者更新...");
for (Observer observer : mObserverList) {
observer.update(this);
}
}
}
public interface Observer {
public void update(Subject subject);
}
public class ConcreteObserver implements Observer {
@Override
public void update(Subject subject) {
if (subject instanceof ConcreteSubject) {
//从主题中拉取数据
String state = ((ConcreteSubject) subject).getState();
System.out.println("观察者收到来自主题的状态:"+state);
}
}
}
推送数据和拉取数据方式的比较,推送的方式可能update方法的参数会发生变化,传递的参数可能是观察者需要的也可能是不需要的,但都会强制推给观察者;拉取的方式直接传递了主题的对象引用,这样传递的参数对象固定不变,观察者可以通过主题对象获取任意它需要的数据。
Java API提供的观察者模式接口
java api当中java.util包提供了一个Observable类以及一个Observer接口,通过这两个东西,我们也可以实现观察者模式。示例代码:
public class JavaSubject extends Observable {
private String mState;
public void changeState(String state) {
this.mState = state;
System.out.println("主题状态发生变化:"+state);
//下面两个是继承Observable的方法,需要更新时调用它们
setChanged();
notifyObservers();
}
public String getState() {
return mState;
}
}
public class JavaObserver implements java.util.Observer {
@Override
public void update(Observable observable, Object o) {
if (observable instanceof JavaSubject) {
String state = ((JavaSubject) observable).getState();
System.out.println("观察者收到来自主题的状态:"+state);
}
}
}
public class Client {
public static void main(String[] args) {
JavaSubject subject = new JavaSubject();
Observer observer = new JavaObserver();
subject.addObserver(observer);
subject.changeState("a new state");
}
}
Java内置的方式很简单,只需要你的主题对象也就是被观察者对象继承Observable类,然后观察者对象实现Observer接口即可。java的Observable类提供了addObserver()和deleteObserver()方法,你可以调用他们进行注册和删除观察者,当被观察者需要改变状态时,先调用setChanged(),之后调用 notifyObservers(),这样就可以通知观察者,在观察者类中update(Observable observable, Object o)则会接收到通知。notifyObservers()也可以带参数,notifyObservers(Object o)这种方式会在update方法的第二个参数中收到这个参数。
Java内置方式的致命缺陷:
Observable是一个类,这意味着你只能去继承它使用而不能使用组合的方式,如果某个类需要同时具有Observable的功能和另一个超类的行为时,就会陷入尴尬的局面,因为java类是不支持多继承的。另外由于必须调用setChanged()方法,而setChanged()是protected的, 所以不能在外部调用。
观察者模式中的性能问题:
观察者模式中一个主题可以注册多个观察者对象,主题在通知观察者更新时,必须遍历观察者对象的集合依次对每一个观察者进行通知,这里会有一个问题就是如果观察者的更新方法执行时间过长是一个耗时任务的话,可能会阻塞后面的观察者更新。解决方式可以采用异步更新的方法,观察者在update方法中使用异步线程处理任务,这样可以立刻返回主题调用处继续下一个观察者的通知。
另外可以使用消息队列(Message Queue)的方式,限制主题中的观察者数量,超出限制的观察者在队列中排队等候,当前面的观察者处理完毕后再处理后面的观察者。
Android中的观察者模式例子:
例如Android中常见的Adapter的notifyDataSetChanged()内部其实就是使用了观察者模式的方式刷新界面的,还有几乎所有UI控件的setXXXListener方法以及我们自定义的xxxListener接口等都属于观察者模式的应用。
参考: