Tim

一枚野生程序员~

  • 主页
  • 分类
  • 标签
  • 归档
  • 关于
所有文章 工具

Tim

一枚野生程序员~

  • 主页
  • 分类
  • 标签
  • 归档
  • 关于

观察者模式

阅读数:次 2020-09-19
字数统计: 2.4k字   |   阅读时长≈ 9分

有一个设计模式帮助你的对象知悉现状,不会错过该对象感兴趣的事情,甚至在对象运行时可决定是否要继续被通知,观察者模式是JDK中使用最多的设计模式之一,非常有用。无论是在JDK还是Android开发当中,我们很容易发现观察者模式的运用之处,如我们经常遇到的点击事件,通过Button控件的诸如Listener 的方法,onClickListener就是观察/订阅到了按钮的点击事件,从而就可以执行对相应的逻辑,不同的动作会有不同的观察者,如单击、长按、连续两次点击等都有对应的Listener。

观察者模式概述

主题 + 订阅者 = 观察者设计模式

现在假设有一个气象站,气象站会根据天气的变化设置新的气象数据 (温度、湿度、气压) ,这些数据会展示在气象看板上面,一旦气象站发布了新的数据,则看板也必须立马更新展示的数据。

mark

在这个例子中,主题就是天气数据,订阅者就是显示装置。一旦有新的天气数据,显示装置立马展示新的数据。

观察者模式代码实现

mark

首先定义一个主题的接口,所有的主题都需要实现这个接口:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
/**
* 主题
*/
public interface Subject {
/**
* 注册观察者
* @param o 观察者对象
*/
void registerObserver(Observer o);

/**
* 移除观察者
* @param o 观察者对象
*/
void removeObserver(Observer o);

/**
* 通知观察者
*/
void notifyObserver();
}

天气数据就是一个主题,因此定义出天气主题的类:

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

public class WeatherData implements Subject {
// 存储了此主题的观察者
private List<Observer> observers;

// 温度
private float temp;
// 湿度
private float humidity;
// 气压
private float pressure;

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

@Override
public void registerObserver(Observer o) {
observers.add(o);
}

@Override
public void removeObserver(Observer o) {
int index = observers.indexOf(o);
if(index >= 0) observers.remove(index);
}

@Override
public void notifyObserver() {
for(Observer o: observers){
o.update(temp, humidity, pressure);
}
}

/**
* 从气象站得到更新的观测值,通知观察者
*/
public void measurementsChanged(){
notifyObserver();
}

/**
* 气象站设置新的值
*/
public void setMeasurements(float temp, float humidity, float pressure){
this.temp = temp;
this.humidity = humidity;
this.pressure = pressure;
measurementsChanged();
}
}

上面的观察者还未定义呢,还是先定义一个统一的观察者数据更新方法的接口

1
2
3
4
5
6
7
8
9
10
11
12
/**
* 观察者
*/
public interface Observer {
/**
* 更新展示板
* @param temp 温度
* @param humidity 湿度
* @param pressure 气压
*/
void update(float temp, float humidity, float pressure);
}

接下来定义一个展示数据的接口,作为显示装置都需要实现的接口:

1
2
3
public interface DisplayElement {
void display();
}

然后就是显示装置的具体实现,目前只实现一种那就是展示最新的气象数据:

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
/**
* 布告板
*/
public class CurrentConditionDisplay implements Observer, DisplayElement{
private float temp;
private float humidity;

public CurrentConditionDisplay(Subject weatherData) {
weatherData.registerObserver(this);
}

public void stopDisplay(Subject weatherData){
weatherData.removeObserver(this);
}

@Override
public void display() {
System.out.println(temp + "℃ " + humidity + "%");
}

@Override
public void update(float temp, float humidity, float pressure) {
this.temp = temp;
this.humidity = humidity;
display();
}
}

接下来测试一下写的观察者模式:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
public class Test {
public static void main(String[] args) {
WeatherData weatherData = new WeatherData();
// 把显示装置注册到WeatherData的观察者列表
CurrentConditionDisplay display = new CurrentConditionDisplay(weatherData);

weatherData.setMeasurements(80, 65, 30.4f);
weatherData.setMeasurements(85, 70, 32.2f);
weatherData.setMeasurements(86, 72, 36.3f);
weatherData.setMeasurements(89, 76, 38.0f);

System.out.println("==========================================");

// 停止观察
display.stopDisplay(weatherData);
weatherData.setMeasurements(91, 77, 39.5f);
weatherData.setMeasurements(97, 79, 40.2f);
}
}

mark

JDK内置的观察者模式

观察者模式是对象的行为模式,在对象之间定义了一对多的依赖关系,就是多个观察者和一个被观察者之间的关系,当被观察者发生变化的时候,会通知所有的观察者对象,他们做出相对应的操作。 在观察者模式,我们又分为推模型和拉模型两种方式,上面演示的内容是推模型。

在JDK内已经有实现好的观察者模式API,java.util包内包含最基本的Observer接口和Observable类,这与我们的Subject接口和Observer接口很相似。实际上Observer接口与Observable类使用起来更方便,因为很多功能已经提前准备好了。下面演示一个通过JDK的API实现拉模型的例子。

1、如何把对象变成观察者

实现观察者接口java.util.Observer,然后调用任何Observable对象的addObserver()方法,不想当观察者的时候,调用deleteObserver()方法即可。

2、被观察者如何送出通知

首先扩展java.util.Observer接口产生被观察者类,然后调用两个方法:

  • 先调用setChanged()方法,标记状态已经改变的事实
  • 然后调用notifyObservers()方法中的一个,notifyObservers()或者notifyObservers(Object arg)

3、观察者如何接收通知

同以前的update()方法一样,只是方法参数略有不同:

1
update(Observable o, Object arg)

第一个参数Observable就是主题对象,好让观察者知道是哪个主题通知它的;第二个参数就是上面的例子中的参数,即数据对象。

如果使用推模式,则可以把数据当做数据对象传入notifyObservers(Object arg)中。否则观察者就必须从被观察者对象中拉取数据,我们把上面气象站的例子重做一次。

4、关于setChanged()

setChanged()方法用于标记状态已经改变的事实,好让notifyObservers()知道当它被调用时就应该更新观察者。如果调用notifyObservers()之前没有调用setChanged(),则观察者不会被通知,伪代码如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
setChaged(){
changed = true
}

notifyObservers(Object arg){
if(changed){
for obs in obsList {
call update(this, arg)
}
changed = false
}
}

notifyObservers(){
notifyObservers(null)
}

这样做的目的就是在更新观察者的时候能有更多的弹性,比如在你想在气象温度变化0.5度以上才通知观察者,就需要通过调用setChanged这样的方式进行数据的有效更新。

JDK内置观察者重做气象站

mark

WeatherData.java

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

public class WeatherData extends Observable {
// 温度
private float temp;
// 湿度
private float humidity;
// 气压
private float pressure;

public WeatherData() { }

public void measurementsChanged(){
setChanged();
notifyObservers();
}

/**
* 气象站设置新的值
*/
public void setMeasurements(float temp, float humidity, float pressure){
this.temp = temp;
this.humidity = humidity;
this.pressure = pressure;
measurementsChanged();
}

// 观察者会利用这些Getter方法取得WeatherData的状态
public float getTemp() {
return temp;
}

public float getHumidity() {
return humidity;
}

public float getPressure() {
return pressure;
}
}

CurrentConditionDisplay.java (DisplayElement和前面的例子一样)

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

public class CurrentConditionDisplay implements Observer, DisplayElement {
private Observable observable;
private float temp;
private float humidity;

public CurrentConditionDisplay(Observable observable) {
this.observable = observable;
observable.addObserver(this);
}

@Override
public void display() {
System.out.println(temp + "℃ " + humidity + "%");
}

@Override
public void update(Observable o, Object arg) {
//System.out.println("被观察者:" + o.getClass().getName());
if(o instanceof WeatherData){
WeatherData weatherData = (WeatherData) o;
this.humidity = weatherData.getHumidity();
this.temp = weatherData.getTemp();
display();
}
}
}

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

观察者模式的优缺点

1、优点

首先是松耦合,当两个对象之间松耦合,它们依旧可以交互,但是不太清楚彼此的细节,观察者模式就提供了这样一种对象设计,让主题和观察者之间松耦合。

主题值需要知道观察者实现了某个接口,也就是Observer接口,不需要知道具体观察者实现类是什么,也不用关系观察者的实现细节。任何时间我们都可以动态的添加或者移除观察者、也包括替换新的观察者等操作,主题都不会受到影响。改变被观察者和观察者任意一方都不会影响另一方,这就是松耦合特点。

2、缺点

接下来说说缺点, 如果一个被观察者对象有很多的直接和间接的观察者的话,将所有的观察者都通知到会花费很多时间,也就是说同一个主题的观察者不能太多,太多了每次通知都是需要消耗时间的。

而且如果在被观察者之间有循环依赖的话,被观察者会触发它们之间进行循环调用,导致系统崩溃。在使用观察者模式是要特别注意这一点。

接下来讨论一个问题,但是却不是观察者模式的问题,而是JDK内置的观察者模式的问题。java.util.Observable是一个类而不是一个接口,如果要使用必须继承这个类,这其实限制了Observable的复用能力,而且通过源码可以看到setChanged()是受保护的权限,这意味着只能继承java.util.Observable,这违反了“多用组合、少用继承”的原则。平时使用的时候应该多注意这个问题,有必要的话最好自己实现一套观察者模式。

参考资料

《Head First 设计模式》

赏

谢谢你请我喝咖啡

支付宝
微信
  • 本文作者: Tim
  • 本文链接: https://zouchanglin.cn/2586075670.html
  • 版权声明: 本博客所有文章除特别声明外,均采用 CC BY-SA 4.0 许可协议。转载请注明出处!
  • Android
  • 观察者模式
  • 设计模式
  • 设计模式

扫一扫,分享到微信

LinkHashMap与LRU
Jsoup实战(正方教务系统爬取)
  1. 1. 观察者模式概述
  2. 2. 观察者模式代码实现
  3. 3. JDK内置的观察者模式
    1. 3.1. 1、如何把对象变成观察者
    2. 3.2. 2、被观察者如何送出通知
    3. 3.3. 3、观察者如何接收通知
    4. 3.4. 4、关于setChanged()
  4. 4. JDK内置观察者重做气象站
  5. 5. 观察者模式的优缺点
    1. 5.1. 1、优点
    2. 5.2. 2、缺点
  6. 6. 参考资料
© 2017-2021 Tim
本站总访问量次 | 本站访客数人
  • 所有文章
  • 工具

tag:

  • 生活
  • Android
  • 索引
  • MySQL
  • 组件通信
  • Nginx
  • JavaSE
  • JUC
  • JavaWeb
  • 模板引擎
  • 前端
  • Linux
  • 计算机网络
  • Docker
  • C/C++
  • JVM
  • 上传下载
  • JavaEE
  • SpringCloud
  • Golang
  • Gradle
  • 网络安全
  • 非对称加密
  • IDEA
  • SpringBoot
  • Jenkins
  • 字符串
  • vim
  • 存储
  • 文件下载
  • Mac
  • Windows
  • NIO
  • RPC
  • 集群
  • 微服务
  • SSH
  • 配置中心
  • XML
  • Chrome
  • 压力测试
  • Git
  • 博客
  • 概率论
  • 排序算法
  • 分布式
  • 异常处理
  • 文件系统
  • 哈希
  • openCV
  • 栈
  • 回溯
  • SpringCore
  • 流媒体
  • rtmp
  • 面向对象
  • Vue
  • ElementUI
  • 软件工程
  • 异步
  • 自定义UI
  • ORM框架
  • 模块化
  • 交互式
  • Jsoup
  • Http Client
  • LRUCache
  • RabbitMQ
  • 消息通信
  • 服务解耦
  • 负载均衡
  • 权限
  • 多线程
  • 单例模式
  • Protobuf
  • 序列化
  • Python
  • m3u8
  • 堆
  • 二叉树
  • 自定义View
  • 观察者模式
  • 设计模式
  • 线程池
  • 动态扩容
  • 高可用
  • GC
  • ffmpeg
  • SpringMVC
  • REST
  • Redis
  • 缓存中间件
  • UML
  • Maven
  • Netty
  • 高性能网络
  • IPC通信
  • IO
  • Stream
  • 发布订阅
  • SQLite
  • Hash
  • 集合框架
  • 链表
  • Lambda
  • 汇编语言
  • 组件化
  • Router
  • 开发工具

    缺失模块。
    1、请确保node版本大于6.2
    2、在博客根目录(注意不是yilia-plus根目录)执行以下命令:
    npm i hexo-generator-json-content --save

    3、在根目录_config.yml里添加配置:

      jsonContent:
        meta: false
        pages: false
        posts:
          title: true
          date: true
          path: true
          text: false
          raw: false
          content: false
          slug: false
          updated: false
          comments: false
          link: false
          permalink: false
          excerpt: false
          categories: false
          tags: true
    

  • 思维导图
  • PDF工具
  • 无损放大
  • 代码转图
  • HTTPS证书