设计模式之观察者模式
设计模式之观察者模式
本篇是设计模式系列博客的第四篇,本篇主要学习设计模式中的第二个行为型模式---观察者模式。
- 什么是观察者模式?
- 模式的结构?
- 模式自定义实现和JDK自带实现?
- 推模型和拉模型?
- JDK源码分析?
- 总结!
1 什么是观察者模式?
引用百度百科中的解释:
观察者模式(有时又被称为模型-视图(View)模式、源-收听者(Listener)模式或从属者模式)是软件设计模式的一种。在此种模式中,一个目标物件管理所有相依于它的观察者物件,并且在它本身的状态改变时主动发出通知。这通常透过呼叫各观察者所提供的方法来实现。此种模式通常被用来实现事件处理系统。
简单点概括成通俗的话来说,就是一个类管理着所有依赖于它的观察者类,并且在它状态变化时会主动给这些依赖它的类发出通知。当然后面我们也可以看到除了主动推消息外也可以拉消息。
2 模式的结构
把它用图表示如下:
image可以看到,我们的被观察者类Observable只关联了一个Observer的列表,然后在自己状态变化时,使用notifyObservers方法通知这些Observer,具体这些Observer都是什么,被观察者是不关心也不需要知道的。
上面就将观察者和被观察者二者的耦合度降到很低了,而我们具体的观察者是必须要知道自己观察的是谁,所以它依赖于被观察者。
3 模式实现
3.1 自定义观察者模式
下面LZ给写出一个很简单的观察者模式-拉模型,来使用JAVA代码简单诠释一下上面的类图。
//这个接口是为了提供一个统一的观察者做出相应行为的方法
public interface Observer {
void update(Observable o);
}
具体的观察者1:
public class ConcreteObserver1 implements Observer{
public void update(Observable o) {
System.out.println("观察者1观察到" + o.getClass().getSimpleName() + "发生变化");
System.out.println("观察者1执行pull拉取动作");
}
}
具体的观察者2:
public class ConcreteObserver2 implements Observer{
public void update(Observable o) {
System.out.println("观察者2观察到" + o.getClass().getSimpleName() + "发生变化");
System.out.println("观察者2执行pull拉取动作");
}
}
下面是被观察者,它有一个观察者的列表,并且有一个通知所有观察者的方法,通知的方式就是调用观察者通用的接口行为update方法。下面我们看它的代码。
public class Observable {
List<Observer> observers = new ArrayList<Observer>();
public void addObserver(Observer o){
observers.add(o);
}
public void changed(){
System.out.println("我是被观察者,我已经发生变化了");
notifyObservers();//通知观察自己的所有观察者
}
public void notifyObservers(){
for (Observer observer : observers) {
observer.update(this);
}
}
}
这里面很简单,新增两个方法,一个是为了改变自己的同时通知观察者们,一个是为了给客户端一个添加观察者的公共接口。
下面我们使用客户端调用一下,看一下客户端如何操作。
public class Client {
public static void main(String[] args) throws Exception {
Observable observable = new Observable();
observable.addObserver(new ConcreteObserver1());
observable.addObserver(new ConcreteObserver2());
observable.changed();
}
}
输出结果:
我是观察者,我已经发生了变化
观察者1观察到Obserable发生了变化
观察者1执行动作
观察者2观察到Obserable发生了变化
观察者2执行动作
3.2 JDK自带的观察者模式
JDK提供了一个Observable(被观察者)抽象类,和一个Observer(观察者)接口。继承前者就可以被当做一个Subject主题,实现后面接口就可以成为Subject的一个观察者。主题需要显示调用setChanged来告诉Observable,我们状态改变需要通知给观察者,也提供了update的重载方法,可以实现推拉两种模式。观察者只需要实现update方法就可以获取被观察者的状态。
4 推模型和拉模型?
4.1 推模型
主题对象向观察者推送主题的详细信息,不管观察者是否需要,推送的信息通常是主题对象的全部或部分数据。
//这个接口是为了提供一个统一的观察者做出相应行为的方法
public interface Observer {
void update();
}
具体的观察者:
public class ConcreteObserver2 implements Observer{
private Observable observable;
public void setObservable(Observable observable){
this.observable =observable;
}
public Observable getObservable(){
return observable;
}
public void update() {
System.out.println("观察者观察到" + observable.getState + "发生变化");
}
}
下面是被观察者,它有一个观察者的列表,并且有一个通知所有观察者的方法,通知的方式就是调用观察者通用的接口行为update方法。下面我们看它的代码。
public class Observable {
private String state;
public void setState(String state){
system.out.pritln("被观察者改变了状态"+state);
this.state =state;
}
public String getState(){
return state;
}
List<Observer> observers = new ArrayList<Observer>();
public void addObserver(Observer o){
observers.add(o);
}
public void notifyObservers(){
for (Observer observer : observers) {
observer.update();
}
}
}
这里面很简单,新增两个方法,一个是为了改变自己的同时通知观察者们,一个是为了给客户端一个添加观察者的公共接口。
下面我们使用客户端调用一下,看一下客户端如何操作。
public class Client {
public static void main(String[] args) throws Exception {
Observable observable = new Observable();
observable.addObserver(new ConcreteObserver1());
observable.addObserver(new ConcreteObserver2());
//被观察者改变状态
observable.setState("change state");
//被观察者推送
observable.update();
}
}
输出结果:
被观察者改变了状态change state
观察者观察到change state发生了变化
4.2 拉模型
主题对象在通知观察者的时候,只传递少量信息。如果观察者需要更具体的信息,由观察者主动到主题对象中获取,相当于是观察者从主题对象中拉数据。一般这种模型的实现中,会把主题对象自身通过update()方法传递给观察者,这样在观察者需要获取数据的时候,就可以通过这个引用来获取了。
这种模式在文章的前面已经给出栗子了。
5. JDK源码分析
其实源码思想跟我们前面自己实现的差别不大只是做了一个封装。先看主题抽象类。代码如下(代码不多我全部贴上,写上必要的注释):
public class Observable {
//通过这个属性来觉得状态是否改变。
private boolean changed = false;
//用一个线程安全的集合列表来保存所有的观察者。这里并没有用到具体的观察者对象而是用的接口,体现了面向接口编程的思想。
private Vector<Observer> obs;
public Observable() {
obs = new Vector<>();
}
//同步添加观察者,同时添加的时候还去重了。
public synchronized void addObserver(Observer o) {
if (o == null)
throw new NullPointerException();
if (!obs.contains(o)) {
obs.addElement(o);
}
}
//取消关注
public synchronized void deleteObserver(Observer o) {
obs.removeElement(o);
}
//通知观察者
public void notifyObservers() {
notifyObservers(null);
}
//传一个对象过去,具体需要什么数据由观察者自己调用,也可以看出这个模式的一个缺点必须是观察要知道有哪个主题?
public void notifyObservers(Object arg) {
Object[] arrLocal;
synchronized (this) {
if (!changed)
return;
arrLocal = obs.toArray();
clearChanged();
}
//循环通知所有的观察者对象
for (int i = arrLocal.length-1; i>=0; i--)
((Observer)arrLocal[i]).update(this, arg);
}
public synchronized void deleteObservers() {
obs.removeAllElements();
}
//改变当前主题的状态
protected synchronized void setChanged() {
changed = true;
}
protected synchronized void clearChanged() {
changed = false;
}
public synchronized boolean hasChanged() {
return changed;
}
public synchronized int countObservers() {
return obs.size();
}
}
现在是观察者接口:
public interface Observer {
//就这么一个方法
void update(Observable o, Object arg);
}
6 总结!
观察者模式所欠缺的是设计上的问题,即观察者和被观察者是多对一的关系,那么反过来的话,就无法支持了。
观察者模式还有一个缺点就是,每一个观察者都要实现观察者接口,才能添加到被观察者的列表当中,假设一个观察者已经存在,而且我们无法改变其代码,那么就无法让它成为一个观察者了,不过这个我们依然可以使用适配器模式解决。但是还有一个问题就不好解决了,就是假如我们很多类都是现成的,当被观察者发生变化时,每一个观察者都需要调用不同的方法,那么观察者模式就有点捉襟见肘的感觉了,我们必须适配每一个类去统一他们变化的方法名称为update,这是一个很可怕的事情。