一起学设计模式 - 观察者模式

文章目录
  1. 1. 概述
  2. 2. 案例
    1. 2.1. 推模式
    2. 2.2. 拉模型
    3. 2.3. JDK中应用
      1. 2.3.1. 小案例
      2. 2.3.2. 观察者模式与MVC
  3. 3. 总结
  4. 4. 说点什么

观察者模式(Observer Pattern)属于对象行为型模式的一种,定义对象之间的一种一对多依赖关系,使得每当一个对象状态发生改变时,其相关依赖对象皆得到通知并被自动更新。

概述

观察者模式是一种使用率极高的模式,用于建立一种对象与对象之间的依赖关系,一个对象发生改变时将自动通知其他对象,其他对象将相应作出反应。在观察者模式中,发生改变的对象称为观察目标,而被通知的对象称为观察者,一个观察目标可以对应多个观察者,而且这些观察者之间可以没有任何相互联系,可以根据需要增加和删除观察者,使得系统更易于扩展。

观察者模式的别名包括发布-订阅(Publish/Subscribe)模式、模型-视图(Model/View)模式、源-监听器(Source/Listener)模式或从属者(Dependents)模式

案例

前言:观察者模式有两种方模型,分别是推模型拉模型

  • 推模型: 主题对象向观察者推送主题的详细信息,不管观察者是否需要,推送的信息通常是主题对象的全部或部分数据。该模式下如果推送数据变了观察者都得改

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

UML结构图

观察者模式UML结构图

  • 抽象主题(Subject)角色: 将观察者对象的引用保存在一个聚集(比如ArrayList对象)里,每个主题都可以有任何数量的观察者。抽象主题提供接口,可以增加和删除观察者对象。抽象主题角色又叫做抽象被观察者(Observable)角色。
  • 具体主题(ConcreteSubject)角色: 将有关状态存入具体观察者对象;在具体主题的内部状态改变时,给所有登记过的观察者发出通知。具体主题角色又叫做具体被观察者(Concrete Observable)角色。
  • 抽象观察者(Observer)角色: 为所有的具体观察者定义一个更新接口,在得到主题的通知时更新自己。
  • 具体观察者(ConcreteObserver)角色: 观察者的具体实现对象,实现抽象观察者角色所要求的更新接口,以便使本身的状态与主题的状态相协调。如果需要,具体观察者角色可以保持一个指向具体主题对象的引用。

推模式

1.定义目标对象,它知道观察它的观察者,并提供注册和删除观察者的接口

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
class Subject {
/**
* 用来保存注册的观察者对象
*/
private List<Observer> observers = new ArrayList<>();

/**
* 注册观察者对象
*
* @param observer 观察者对象
*/
void attach(Observer observer) {
observers.add(observer);
}

/**
* 通知所有注册的观察者对象
*/
void notifyObservers(String newState) {
for (Observer observer : observers) {
observer.update(newState);
}
}
}

2.具体的目标对象,负责把有关状态存入到相应的观察者对象,并在自己状态发生改变时,通知各个观察者

1
2
3
4
5
6
7
8
9
10
11
12
13
14
class ConcreteSubject extends Subject {

private String subjectState;

public String getSubjectState() {
return subjectState;
}

public void change(String subjectState) {
this.subjectState = subjectState;
//状态发生改变,通知各个观察者
this.notifyObservers(subjectState);
}
}

3.创建观察者接口,定义一个更新的接口给那些在目标发生改变的时候被通知的对象

1
2
3
4
5
6
7
8
interface Observer {
/**
* 更新的接口
*
* @param subject 传入目标对象,好获取相应的目标对象的状态
*/
void update(String subject);
}

4.具体观察者对象,实现更新的方法,使自身的状态和目标的状态保持一致

1
2
3
4
5
6
7
8
9
class ConcreteObserver implements Observer {

@Override
public void update(String newState) {
//具体的更新实现
//这里可能需要更新观察者的状态,使其与目标的状态保持一致
System.out.println("接收到:" + newState);
}
}

5.创建推模型客户端,用于测试

1
2
3
4
5
6
7
8
9
10
11
12
13
14
public class PushClient {

public static void main(String[] args) {
//创建主题对象
ConcreteSubject subject = new ConcreteSubject();
//创建观察者对象
Observer observer = new ConcreteObserver();
//将观察者对象登记到主题对象上
subject.attach(observer);
//改变主题对象的状态
subject.change("push state");
}

}

6.运行结果

1
接收到:push state

拉模型

1.定义目标对象,它知道观察它的观察者,并提供注册和删除观察者的接口

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
class Subject {
/**
* 用来保存注册的观察者对象
*/
private List<Observer> observers = new ArrayList<>();

/**
* 注册观察者对象
*
* @param observer 观察者对象
*/
public void attach(Observer observer) {
observers.add(observer);
}

/**
* 通知所有注册的观察者对象
*/
public void notifyObservers() {
for (Observer observer : observers) {
// 注意这句代码'
observer.update(this);
}
}
}

2.具体的目标对象,负责把有关状态存入到相应的观察者对象,并在自己状态发生改变时,通知各个观察者

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
class ConcreteSubject extends Subject {
/**
* 示意,目标对象的状态
*/
private String subjectState;

public String getSubjectState() {
return subjectState;
}

public void change(String subjectState) {
this.subjectState = subjectState;
//状态发生改变,通知各个观察者
this.notifyObservers();
}
}

3.创建观察者接口,定义一个更新的接口给那些在目标发生改变的时候被通知的对象

1
2
3
4
5
6
7
8
interface Observer {
/**
* 更新的接口
*
* @param subject 传入目标对象,好获取相应的目标对象的状态
*/
void update(Subject subject);
}

4.具体观察者对象,实现更新的方法,使自身的状态和目标的状态保持一致

1
2
3
4
5
6
7
8
9
10
11
12
13
14
class ConcreteObserver implements Observer {
/**
* 示意,观者者的状态
*/
private String observerState;

@Override
public void update(Subject subject) {
//具体的更新实现
//这里可能需要更新观察者的状态,使其与目标的状态保持一致
observerState = ((ConcreteSubject) subject).getSubjectState();
System.out.println("接收到:" + observerState);
}
}

5.创建拉模型客户端,用于测试

1
2
3
4
5
6
7
8
9
10
11
12
13
14
public class PullClient {

public static void main(String[] args) {
//创建主题对象
ConcreteSubject subject = new ConcreteSubject();
//创建观察者对象
Observer observer = new ConcreteObserver();
//将观察者对象登记到主题对象上
subject.attach(observer);
//改变主题对象的状态
subject.change("pull state");
}

}

6.运行结果

1
接收到:pull state

上文说过推模型是假定主题对象知道观察者需要的数据,这种模型下如果数据发生变更会造成极大的影响;而拉模型是主题对象不知道观察者具体需要什么数据,没有办法的情况下,干脆把自身传递给观察者,让观察者自己去按需要取值。由此可见:拉模式的适用范围更广;

JDK中应用

对于观察者模式,其实Java已经为我们提供了已有的接口和类。对于订阅者(Subscribe,观察者)Java为我们提供了一个接口。

UML图

JDK自带

在JAVA语言的 java.util 库里面,提供了一个Observable类以及一个Observer接口,构成JAVA语言对观察者模式的支持。

Observer: 只定义了一个 update() 方法,当被观察者对象的状态发生变化时,被观察者对象的 notifyObservers() 方法就会调用这一方法。

1
2
3
public interface Observer {
void update(Observable o, Object arg);
}

Observable: 充当观察目标类,在Observable中定义了一个向量Vector来存储观察者对象。一个观察目标类可以有多个观察者对象,每个观察者对象都是实现Observer接口的对象。在被观察者发生变化时,会调用ObservablenotifyObservers()方法,此方法调用所有的具体观察者的update()方法, 从而使所有的观察者都被通知更新自己。

  • setChanged() 设置一个内部标记变量,代表被观察者对象的状态发生了变化。
  • notifyObservers()调用所有登记过的观察者对象的update()方法,使这些观察者对象可以更新自己。
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
63
64
65
66
public class Observable {
private boolean changed = false; //是否改变状态,每次都需要设置,表示内容发生变化
private Vector<Observer> obs; //Vector利用同步方法来线程安全,线程安全在多线程情况下不会造成数据混乱

/** Construct an Observable with zero Observers. */

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);
}

//通知方法,用于在方法内部循环调用向量中每一个观察者的update()方法。
public void notifyObservers() {
notifyObservers(null);
}


public void notifyObservers(Object arg) {

Object[] arrLocal;

synchronized (this) {
if (!changed) //状态值未改变时返回,不通知
return;
arrLocal = obs.toArray(); //将Vector转换成数组
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.定义两个实现了实现java.util.Observer接口的观察者

1
2
3
4
5
6
7
8
9
10
11
12
13
class SubscribeReader implements Observer {
@Override
public void update(Observable o, Object arg) {
System.out.println("开始读取:" + ((Publish) o).getMessage());
}
}

class SubscribeWrite implements Observer {
@Override
public void update(Observable o, Object arg) {
System.out.println("开始写入:" + ((Publish) o).getMessage());
}
}

2.创建继承java.util.Observable的通知者

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
class Publish extends Observable {
private String message;

public String getMessage() {
return message;
}

public void setMessage(String message) {
this.message = message;
//改变通知者的状态
super.setChanged();
//调用父类Observable方法,通知所有观察者
super.notifyObservers();
}
}

3.创建测试客户端

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
public class Client {

public static void main(String[] args) {
Publish publish = new Publish();
// 遵循FIFO 模型 先进后出
SubscribeWrite write = new SubscribeWrite();
SubscribeReader reader = new SubscribeReader();
publish.addObserver(reader);
publish.addObserver(write);
publish.setMessage("Hello Battcn");
publish.setMessage("QQ:1837307557");
publish.setMessage("Email:1837307557@qq.com");
}

}

4.运行结果

1
2
3
4
5
6
开始写入:Hello Battcn
开始读取:Hello Battcn
开始写入:QQ:1837307557
开始读取:QQ:1837307557
开始写入:Email:1837307557@qq.com
开始读取:Email:1837307557@qq.com

观察者模式与MVC

在当前流行的MVC(Model-View-Controller)架构中也应用了观察者模式,MVC是一种架构模式,它包含三个角色:模型(Model),视图(View)和控制器(Controller)。其中模型可对应于观察者模式中的观察目标,而视图对应于观察者,控制器可充当两者之间的中介者。当模型层的数据发生改变时,视图层将自动改变其显示内容。

总结

实现的关键是要建立观察者和被观察者之间的联系、比如在被观察者类中有个集合是用于存放观察者的、当被检测的东西发生改变的时候就要通知所有观察者。在被观察者中要提供一些对所有观察者管理的一些方法.目的是添加或者删除一些观察者.这样才能让被观察者及时的通知观察者关系的状态已经改变、并且调用观察者通用的方法将变化传递过去。

在实现观察者模式,如果JDK的Observable类和一个Observer接口能满足需求,直接复用即可,无需自己编写抽象观察者、抽象主题类;

但是,java.util.Observable是一个类而不是接口,你必须设计一个类继承它。如果某个类想同时具有Observable类和另一个超类的行为,由于java不支持多重继承。所以这个时候就需要自己实现一整套观察者模式。

优点

  • 可实现表示层和数据逻辑层的分离,定义了稳定的消息更新传递机制,并抽象了更新接口,使得可以有各种各样不同的表示层充当具体观察者角色(Model/View)
  • 支持广播通信,观察目标会向所有已注册的观察者对象发送通知,简化了一对多系统设计的难度(Publish/Subscribe)
  • 实现动态联动。由于观察者模式对观察者注册实行管理,那就可以在运行期间,通过动态的控制注册的观察者,来控制某个动作的联动范围,从而实现动态联动。

缺点

  • 如果一个被观察者对象有很多直接和间接的观察者,那么将所有的观察者都通知到会花费很多时间。
  • 如果在观察者和被观察者之间有循环依赖的话,被观察者会触发它们形成循环调用,可能导致系统崩溃。
  • 观察者模式没有相应的机制让观察者知道被观察者对象是怎么发生变化的,而仅仅只是知道被观察者发生了变化。

观察者模式是一种使用频率非常高的设计模式,无论是移动应用、Web应用或者桌面应用,观察者模式几乎无处不在,它为实现对象之间的联动提供了一套完整的解决方案,凡是涉及到一对一或者一对多的对象交互场景都可以使用观察者模式。

说点什么

参考文献:http://www.cnblogs.com/JsonShare/p/7270546.html

全文代码:https://gitee.com/battcn/design-pattern/tree/master/Chapter17/battcn-observer

  • 个人QQ:1837307557
  • battcn开源群(适合新手):391619659

微信公众号:battcn(欢迎调戏)