设计模式(十四)之行为型模式-观察者模式

一、观察者模式

1、 案例

1.1 天气预报项目需求

  1. 气象站可以将每天测量到的温度,湿度,气压等等以公告的形式发布出去(比如发布到自己的网站或第三方)。
  2. 需要设计开放型API,便于其他第三方也能接入气象站获取数据
  3. 提供温度、气压和湿度的接口
  4. 测量数据更新时,要能实时的通知给第三方

1.2 普通方案1

通过对气象站项目的分析,我们可以初步设计出一个WeatherData类

image-20210807095116719

说明:

  1. 通过getXxx方法,可以让第三方接入,并得到相关信息
  2. 当数据有更新时,气象站通过调用dataChange()去更新数据,当第三方再次获取时,就能得到最新数据,当然也可以推送

推送示意图如下:

​ 在WeatherData中,每隔一段时间,将dataChange()推送至CurrentConditions中的update方法

CurrentConditions(当前的天气情况)可以理解成是我们气象局的网站//推送

image-20210807095529152

1.3 普通方案代码实例

1.3.1 CurrentConditions类

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
//显示当前天气情况(可以理解成气象站自己的网站)
public class CurrentConditions {
//温度、气压、湿度
private float temperature;
private float pressure;
private float humidity;

//更新天气情况,是由WeatherData来调用, 使用推送模式
public void update(float temperature,float pressure,float humidity){
this.temperature = temperature;
this.pressure = pressure;
this.humidity = humidity;
display();
}

//显示
public void display(){
System.out.println("***Today mTempature: "+temperature + "***");
System.out.println("***Today mPressure: "+pressure + "***");
System.out.println("***Today mHumidity: "+humidity + "***");
}
}

1.3.2 WeatherData类

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
//类是核心
// 1. 包含最新的天气情况信息
// 2. 含有CurrentConditions对象
// 3. 当数据有更新时,就主动的调用CurrentConditions对象update方法(含display),这样他们(接入方)就看到最新的消息
public class WeatherData {
private float temperature;
private float pressure;
private float humidity;
private CurrentConditions currentConditions;

public WeatherData(CurrentConditions currentConditions) {
this.currentConditions = currentConditions;
}

public float getTemperature() {
return temperature;
}

public float getPressure() {
return pressure;
}

public float getHumidity() {
return humidity;
}

public CurrentConditions getCurrentConditions() {
return currentConditions;
}

public void dataChange(){
currentConditions.update(getTemperature(),getPressure(),getHumidity());
}

//当数据有更新时,
public void setData(float temperature,float pressure,float humidity){
this.temperature = temperature;
this.pressure = pressure;
this.humidity = humidity;
dataChange();//调用dataChange,将最新的信息推送给接入方
}
}

1.3.3 Client类

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
public class Client {
public static void main(String[] args) {
//创建接入方 CurrentConditions
CurrentConditions currentConditions = new CurrentConditions();
//创建WeatherData 并将接入方 currentConditions 传递到WeatherData中
WeatherData weatherData = new WeatherData(currentConditions);

//更新天气预报
weatherData.setData(30,150,40);

//天气情况变化
System.out.println("==============天气情况变化================");
weatherData.setData(40,160,20);

}
}

结果:

1
2
3
4
5
6
7
***Today mTempature: 30.0***
***Today mPressure: 150.0***
***Today mHumidity: 40.0***
==============天气情况变化================
***Today mTempature: 40.0***
***Today mPressure: 160.0***
***Today mHumidity: 20.0***

1.4 普通方案问题分析

  1. 其他第三方接入气象站获取数据的问题
  2. 无法在运行时动态的添加第三方

//在WeatherData中,当增加一个第三方,都需要创建一个对应的第三方的公告板对象,并加入到dataChange,不利于维护,也不是动态加入

3.违反了开闭原则,添加新的第三方时,会在下面的代码中进行修改

1
2
3
4
5
6
7
public WeatherData(CurrentConditions currentConditions) {
this.currentConditions = currentConditions;
}

public void dataChange(){
currentConditions.update(getTemperature(),getPressure(),getHumidity());
}

2、观察者模式原理

2.1 Subject

观察者模式类似于订牛奶业务

  1. 奶站(气象局):Subject
  2. 用户/第三方网站:Observer

image-20210808105847511

说明:

  1. Subject:登记注册、移除和通知
  2. registerObserver注册
  3. removeObserver移除
  4. notifyObservers()通知所有的注册的用户,根据不同得到需求,可以是更新数据,让用户来取,也可以是实施推送,看具体需求定

2.2 Observer

image-20210808110811147

说明:

  1. 观察者模式:对象之间多对一依赖的一种设计方案,被依赖的对象为Subject,依赖的对象是Observer,Subject通知Observer变化,比如这里的奶站是Subject,是1的一方。用户是Observer,是多的一方。

2.3 原理类图

image-20210808200922650

3、代码实例

3.1 Observer接口

1
2
3
4
//观察者接口,由观察者来实现
public interface Observer {
public void update(float tempterature,float pressure,float humidity);
}

3.2 Subject接口

1
2
3
4
5
public interface Subject {
public void registerObserver(Observer o);
public void removeObserver(Observer o);
public void notifyObservers();
}

3.3 CurrentConditions类

实现Observer接口

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
public class CurrentConditions implements  Observer{
//温度、气压、湿度
private float temperature;
private float pressure;
private float humidity;

//更新天气情况,是由WeatherData来调用, 使用推送模式
public void update(float temperature,float pressure,float humidity){
this.temperature = temperature;
this.pressure = pressure;
this.humidity = humidity;
display();
}

//显示
public void display(){
System.out.println("***Today mTempature: "+temperature + "***");
System.out.println("***Today mPressure: "+pressure + "***");
System.out.println("***Today mHumidity: "+humidity + "***");
}
}

3.4 WeatherData类

实现Subject接口

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
import java.util.ArrayList;

//类是核心
// 1. 包含最新的天气情况信息 使用ArrayList管理
// 2. 含有 观察者集合,
// 3. 当数据有更新时,就主动的调用ArrayList,通知所有的(接入方)就看到最新的信息

public class WeatherData implements Subject{
private float temperature;
private float pressure;
private float humidity;
//观察者集合
private ArrayList<Observer> observers;

public WeatherData( ){
observers = new ArrayList<Observer>();
}

public float getTemperature() {
return temperature;
}

public float getPressure() {
return pressure;
}

public float getHumidity() {
return humidity;
}


public void dataChange(){
notifyObservers();
}

//当数据有更新时,
public void setData(float temperature,float pressure,float humidity){
this.temperature = temperature;
this.pressure = pressure;
this.humidity = humidity;
dataChange();//调用dataChange,将最新的信息推送给接入方
}

//注册一个观察者
@Override
public void registerObserver(Observer o) {
observers.add(o);
}

//移除一个观察者
@Override
public void removeObserver(Observer o) {
if(observers.contains(o))
observers.remove(o);
}

//遍历所有的观察者,并通知
@Override
public void notifyObservers() {
for(int i = 0;i < observers.size(); i++){
observers.get(i).update(this.temperature,this.pressure,this.humidity);
}
}
}

3.5 Client类

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
public class Client {
public static void main(String[] args) {
//创建一个WeatherData
WeatherData weatherData = new WeatherData();

//创建观察者
CurrentConditions currentConditions = new CurrentConditions();

//注册到weatherData
weatherData.registerObserver(currentConditions);

//测试
System.out.println("通知各个注册的观察者,看看信息");
weatherData.setData(10f,100f,30.3f);
}
}

结果:

1
2
3
4
通知各个注册的观察者,看看信息
***Today mTempature: 10.0***
***Today mPressure: 100.0***
***Today mHumidity: 30.3***

3.6 添加新的观察者

3.6.1 BaiduSite类

实现Observer接口

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
public class BaiduSite implements Observer{
//温度、气压、湿度
private float temperature;
private float pressure;
private float humidity;

//更新天气情况,是由WeatherData来调用, 使用推送模式
public void update(float temperature,float pressure,float humidity){
this.temperature = temperature;
this.pressure = pressure;
this.humidity = humidity;
display();
}

//显示
public void display(){
System.out.println("百度网站");
System.out.println("***百度网站 气温: "+temperature + "***");
System.out.println("***百度网站 气压: "+pressure + "***");
System.out.println("***百度网站 湿度: "+humidity + "***");
}
}

3.6.2 Client类

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
public class Client {
public static void main(String[] args) {
//创建一个WeatherData
WeatherData weatherData = new WeatherData();

//创建观察者
CurrentConditions currentConditions = new CurrentConditions();
BaiduSite baiduSite = new BaiduSite();

//注册到weatherData
weatherData.registerObserver(currentConditions);
weatherData.registerObserver(baiduSite);

//测试
System.out.println("通知各个注册的观察者,看看信息");
weatherData.setData(10f,100f,30.3f);
}
}

结果:

1
2
3
4
5
6
7
8
通知各个注册的观察者,看看信息
***Today mTempature: 10.0***
***Today mPressure: 100.0***
***Today mHumidity: 30.3***
百度网站
***百度网站 气温: 10.0***
***百度网站 气压: 100.0***
***百度网站 湿度: 30.3***

4、观察者模式好处

  1. 观察者模式设计后,会以集合的方式来管理用户(Observer),包括注册、移除和通知
  2. 这样,我们增加观察者(这里可以理解成一个新的公告板),就不需要去修改核心类WeatherData,不会修改代码,遵守了ocp原则

5、观察者模式在Jdk应用的源码分析

5.1 源码分析

(1)new 一个Obserable对象

1
2
3
public static void main(String[] args) {
new Observable();
}

(2)Obserable类

里面含有Observer,还有3个方法addObserver、deleteObserver、notifyObservers

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
public class Observable {
private boolean changed = false;
private Vector<Observer> obs;

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

public Observable() {
obs = new Vector<>();
}
public synchronized void addObserver(Observer o){...}

public synchronized void deleteObserver(Observer o) {
obs.removeElement(o);
}

public void notifyObservers() {
notifyObservers(null);
}
}

(3)Observer接口

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

(4)Obserable类中的notifyObservers方法详解

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

5.2 说明

  1. Observable的作用和地位等价于我们前面讲过的Subject
  2. Observable是类,不是接口,类中已经实现了核心的方法,即管理Observer的方法,add…,delete…,notiry…
  3. Observer的作用和地位等价于我们前面讲过的Observer,有update
  4. Observable和Observer的使用方法和前面讲过的一样,只是Observable是类,通过继承来实现观察者模式
0%