Fork me on GitHub

设计模式之观察者模式

设计模式之观察者模式

本篇是设计模式系列博客的第四篇,本篇主要学习设计模式中的第二个行为型模式—观察者模式。

  1. 什么是观察者模式?
  2. 模式的结构?
  3. 模式自定义实现和JDK自带实现?
  4. 推模型和拉模型?
  5. JDK源码分析?

1 什么是观察者模式?

引用百度百科中的解释:

观察者模式(有时又被称为模型-视图(View)模式、源-收听者(Listener)模式或从属者模式)是软件设计模式的一种。在此种模式中,一个目标物件管理所有相依于它的观察者物件,并且在它本身的状态改变时主动发出通知。这通常透过呼叫各观察者所提供的方法来实现。此种模式通常被用来实现事件处理系统。

简单点概括成通俗的话来说,就是一个类管理着所有依赖于它的观察者类,并且在它状态变化时会主动给这些依赖它的类发出通知。当然后面我们也可以看到除了主动推消息外也可以拉消息。

2 模式的结构

把它用图表示如下:

image

可以看到,我们的被观察者类Observable只关联了一个Observer的列表,然后在自己状态变化时,使用notifyObservers方法通知这些Observer,具体这些Observer都是什么,被观察者是不关心也不需要知道的。

上面就将观察者和被观察者二者的耦合度降到很低了,而我们具体的观察者是必须要知道自己观察的是谁,所以它依赖于被观察者。

3 模式实现

3.1 自定义观察者模式

下面LZ给写出一个很简单的观察者模式-拉模型,来使用JAVA代码简单诠释一下上面的类图。

1
2
3
4
5
6
//这个接口是为了提供一个统一的观察者做出相应行为的方法
public interface Observer {

void update(Observable o);

}

具体的观察者1:

1
2
3
4
5
6
7
8
public class ConcreteObserver1 implements Observer{

public void update(Observable o) {
System.out.println("观察者1观察到" + o.getClass().getSimpleName() + "发生变化");
System.out.println("观察者1执行pull拉取动作");
}

}

具体的观察者2:

1
2
3
4
5
6
7
8
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);
    }
}

}

这里面很简单,新增两个方法,一个是为了改变自己的同时通知观察者们,一个是为了给客户端一个添加观察者的公共接口。
下面我们使用客户端调用一下,看一下客户端如何操作。

1
2
3
4
5
6
7
8
9
10
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
2
3
4
5
我是观察者,我已经发生了变化
观察者1观察到Obserable发生了变化
观察者1执行动作
观察者2观察到Obserable发生了变化
观察者2执行动作

3.2 JDK自带的观察者模式

JDK提供了一个Observable(被观察者)抽象类,和一个Observer(观察者)接口。继承前者就可以被当做一个Subject主题,实现后面接口就可以成为Subject的一个观察者。主题需要显示调用setChanged来告诉Observable,我们状态改变需要通知给观察者,也提供了update的重载方法,可以实现推拉两种模式。观察者只需要实现update方法就可以获取被观察者的状态。

4 推模型和拉模型?

4.1 推模型

主题对象向观察者推送主题的详细信息,不管观察者是否需要,推送的信息通常是主题对象的全部或部分数据。

1
2
3
4
5
6
//这个接口是为了提供一个统一的观察者做出相应行为的方法
public interface Observer {

void update();

}

具体的观察者:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
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();
    }
}

}

这里面很简单,新增两个方法,一个是为了改变自己的同时通知观察者们,一个是为了给客户端一个添加观察者的公共接口。
下面我们使用客户端调用一下,看一下客户端如何操作。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
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();
}
}

输出结果:

1
2
3
被观察者改变了状态change state

观察者观察到change state发生了变化

4.2 拉模型

主题对象在通知观察者的时候,只传递少量信息。如果观察者需要更具体的信息,由观察者主动到主题对象中获取,相当于是观察者从主题对象中拉数据。一般这种模型的实现中,会把主题对象自身通过update()方法传递给观察者,这样在观察者需要获取数据的时候,就可以通过这个引用来获取了。

这种模式在文章的前面已经给出栗子了。

5. JDK源码分析

其实源码思想跟我们前面自己实现的差别不大只是做了一个封装。先看主题抽象类。代码如下(代码不多我全部贴上,写上必要的注释):

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
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();
}
}

现在是观察者接口:

1
2
3
4
public interface Observer {
//就这么一个方法
void update(Observable o, Object arg);
}

6 总结

观察者模式所欠缺的是设计上的问题,即观察者和被观察者是多对一的关系,那么反过来的话,就无法支持了。

观察者模式还有一个缺点就是,每一个观察者都要实现观察者接口,才能添加到被观察者的列表当中,假设一个观察者已经存在,而且我们无法改变其代码,那么就无法让它成为一个观察者了,不过这个我们依然可以使用适配器模式解决。但是还有一个问题就不好解决了,就是假如我们很多类都是现成的,当被观察者发生变化时,每一个观察者都需要调用不同的方法,那么观察者模式就有点捉襟见肘的感觉了,我们必须适配每一个类去统一他们变化的方法名称为update,这是一个很可怕的事情。

-------------���Ľ�����л�����Ķ�-------------