2、观察者模式

夏婷大大_9072

夏婷大大_9072

2016-06-07

1487人浏览

原创

整理: 气象站的故事 现在我们要为一家气象站开发一套气象监控系统,按照客户的要求,这个监控系统必须可以实时跟踪当前的天气状况(温度、湿度、大气压力),并且可以在三种不同设备上显示出来(当前天气状况、天气统计、天气预测)。客户还希望这个系统可以对外

整理:

气象站的故事

       现在我们要为一家气象站开发一套气象监控系统,按照客户的要求,这个监控系统必须可以实时跟踪当前的天气状况(温度、湿度、大气压力),并且可以在三种不同设备上显示出来(当前天气状况、天气统计、天气预测)。客户还希望这个系统可以对外提供一个API接口,以便任何开发者都可以开发自己的显示设备,然后无缝挂接到系统中,系统可以统一更新所有显示设备的数据。客户还会提供一个可以访问气象站的硬件设备的组件,如下图所示:

       它提供了三个方法(get开头),可以分别取得实时的温度、湿度和大气压力,还有一个MeasurementsChanged()方法,当任何天气状况发生变化的时候,这个方法都会自动被触发,当前这个方法只是一个空函数,扩展的代码还需要我们自己去扩充。至于WeatherData是如何取得天气状况的,还有MeasurementsChanged()方法是如何被自动触发的这些事情都不需要我们去考虑,我们只管考虑如果做好跟显示设备有关的事情就好了。  

    OK!让我们来考虑一下这个系统的实现,先重新理一下思路:

1.  客户提供了获取实时的天气状况的方法。

2.  MeasurementsChanged()方法会在天气状况变化时被自动调用。

3.  系统要实现三种显示模式,分别显示天气状况、天气统计和天气预测,而且这些显示的信息必须跟当前最新的天气状况实时同步。

4.  系统还必须支持在显示方式上的扩展性,而且使用者可以任意添加和移除不同的显示模式。

基于上面这些信息,我们大概都会想到可以象下面这样来实现这个系统:

 

    //伪代码

    public class WeatherData

    {       

        //实例化显示设备(省略)

        public void MeasurementsChanged()

        {

            float temp = getTemperature(); //取得温度

            float humidity = getHumidity(); //取得湿度

            float pressure = getPressure(); //取得气压

            currentConditionsDisplay.update(temp, humidity, pressure); //同步显示当前天气状况

            statisticsDisplay.update(temp, humidity, pressure); //同步显示天气统计信息

            forecastDisplay.update(temp, humidity, pressure); //同步显示天气预报信息

        }

}

 

    因为客户已经给我们提供了实时的数据,还提供了数据更新时候的触发机制,那么我们要做的就是把最新的数据提供给不同的显示设备就OK了,上面的代码好象已经可以基本解决问题啦。哈哈!

面向接口编程,而不要面向实现编程。”的原则,这样实现会带来的问题是系统无法满足在不修改代码的情况下动态添加或移除不同的显示设备。换句话说,显示设备相关的部分是系统中最不稳定的部分,应该将其单独隔离开,也就是前面学过的另一个原则:“找到系统中变化的部分,将变化的部分同其它稳定的部分隔开。”那么我们到底该怎么办呢?呵呵,既然这篇文章是讲观察者模式的,当然要用它来结束战斗!下面我们先来认识一下观察者模式~

这就是观察者模式

       我们还是先看一下官方的定义:

       The Observer Pattern defines a one-to-many dependency between objects so that when one object changes state, all of its dependents are notified and updated automatically. (观察者模式定义了对象间的一种一对多依赖关系,使得每当一个对象改变状态,则所有依赖于它的对象都会得到通知并被自动更新)

       咋样?这是超级经典的标准定义,如假抱换的!不懂?那再看看下面的类图吧~

Subject(被观察的对象接口)

  

l         规定ConcreteSubject的统一接口;

l         每个Subject可以有多个Observer;

ConcreteSubject(具体被观察对象)

l         维护对所有具体观察者的引用的列表;

l         状态发生变化时会发送通知给所有注册的观察者。

Observer(观察者接口)

l         规定ConcreteObserver的统一接口;

l         定义了一个update()方法,在被观察对象状态改变时会被调用。

ConcreteObserver(具体观察者)

l         维护一个对ConcreteSubject的引用;

l         特定状态与ConcreteSubject同步;

l         实现Observer接口,通过update()方法接收ConcreteSubject的通知。

响应式天气预报宣传网站模板
响应式天气预报宣传网站模板

响应式天气预报宣传网站模板是一款适合天文预报服务宣传网站模板下载。提示:本模板调用到谷歌字体库,可能会出现页面打开比较缓慢。

下载

      

怎么样,现在总该有点感觉了吧?下面还有一个顺序图,再体会体会~

 

 

       呵呵!还没想明白,为什么官方的东西总是看不懂,看来是没当官的命啦!其实观察者模式十分简单,现实生活中的例子更是随处可见,就比如看电视:某个观众就是一个标准的ConcreteObserver(具体观察者,都符合统一的Observer接口,即都要通过电视收看节目的观众),电视节目就是Subject(被观察对象接口,这里体现为无线电视信号)了,不同的频道的节目是不同的ConcreteSubject(不同频道有不同的节目),观众可以自由决定看电视(registerObserver)或不看电视(removeObserver),而电视节目的变化也会在自动更新(notifyObservers)所有观众的收看内容。怎么样?这回明白了吧!

       另外观察者模式也叫发布-订阅模式(Publishers + Subscribers = Observer Pattern),跟看电视一样,订阅报纸也是一个很直观的例子,有人发布(Publish = Subject)报纸,有人订阅(Subscribe = Observer)报纸,订阅的人可以定期收到最新发布的报纸,订阅人也可以随时退订。

       现在大家应该对观察者模式基本都了解了,我们来用这个模式来解决气象站哪个问题。就气象站问题的应用场景来说,WeatherData可以作为ConcreteSubject来看待,而不同的显示设备则可以作为ConcreteObserver来看待,也就是说显示设备观察WeatherData对象,如果WeatherData对象有任何状态变化,则立刻更新显示设备的数据信息。这么说似乎很靠谱了,下面我们再来具体实现一下吧,先从整体结构开始,如下类图:

       跟前面说的实现方式完全一样,只是这里为所有显示设备又定义了一个统一的接口,这个接口里定义了一个display()方法,也就是说未来所有实现Observer和DisplayElement接口的对象应该都可以作为气象监控系统的终端显示设备,不同用户可以在display()方法里任意自定义自己的显示模式。因为为了防止混乱,图4中只画了一个具体显示设备对象,即CurrentConditionsDisplay,跟它同级别的还有StatisticsDisplay和ForcastDisplay,它们在结构上完全相同。下面我们通过具体的代码再进一步理解一下基于观察者模式的气象监控系统的实现。  

ISubject:

public interface Subject {
public void registerObserver(Observer observer);
public void removeObserver(Observer observer);
public void notifyObservers();
}


关于这段代码,似乎没什么好说的了,因为上面已经反复说了很多啦。

IObserver:

public interface Observer {
public void update(float temp, float humidity, float pressure);
}

这里我们给update()方法定义了三个对应不同气象数据的参数。

DisplayElement :

public interface DisplayElement {
public void display();
}


这个类也是超级简单,没什么可解释的。

WeatherData:

public class WeatherData implements Subject {
private ArrayList observers;
private float temp;
private float humidity;
private float pressure;
public WeatherData() {
this.observers = new ArrayList();
}

@Override
public void notifyObservers() {
for (int i = 0; i = 0) {
observers.remove(i);
}
}

public void measurementsChanged() {
notifyObservers();
}


public void setMeasurements(float temp, float humidity, float pressure) {
this.temp = temp;
this.humidity = humidity;
this.pressure = pressure;
measurementsChanged();
}
}


这个类是ISubject的具体实现,内部使用ArrayList来记录所有注册的观察者,SetMeasurements() 方法是用来模拟前面提到的在天气状况改变的时候自动触发MeasurementsChanged()方法的机制。

CurrentConditionsDisplay:

public class CurrentConditionDisplay implements Observer, DisplayElement {
private Subject weatherData;
private float temp;
private float humidity;
public CurrentConditionDisplay(Subject weatherData) {
this.weatherData = weatherData;
weatherData.registerObserver(this);
}
@Override
public void update(float temp, float humidity, float pressure) {
this.temp = temp;
this.humidity = humidity;
display();
}

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

 

这个类是IObserver和IDisplayElement的具体实现,代表显示当前天气状况的具体显示设备对象,其内部维护了一个ISubject类型的变量,该变量在CurrentConditionsDisplay的构造函数中被初始化,同时调用ISubject.registerObserver()方法,实现订阅ISubject。

StatisticsDisplay和ForcastDisplay:

public class ForecastDisplay implements Observer, DisplayElement {
private Subject weatherData;
private float temp;
private float humidity;
private float pressure;
public ForecastDisplay(Subject weatherData) {
this.weatherData = weatherData;
weatherData.registerObserver(this);
}
@Override
public void update(float temp, float humidity, float pressure) {
this.temp = temp;
this.humidity = humidity;
this.pressure = pressure;
display();
}
@Override
public void display() {
System.out.println("ForecastDisplay   temp = " + temp + " pressure = "
+ pressure);
}
}



StatisticsDisplay代码类似。

测试代码:

public class WeatherTest {
public static void main(String[] args) {
WeatherData weatherData = new WeatherData();
CurrentConditionDisplay currentDisplay = new CurrentConditionDisplay(
weatherData);
ForecastDisplay forecastDisplay = new ForecastDisplay(weatherData);

weatherData.setMeasurements(80f, 90f, 30.4f);

weatherData.setMeasurements(100f, 99f, 40.0f);
}
}

..

使用JDK内置的观察者模式

若使用java内置的观察者模式,则 Subject 需要继承 java.util.Observable 类和, 观察者实现java.util.Observer接口,具体代码如下:

public class WeatherData extends Observable {

private float temp;
private float humidity;
private float pressure;

public WeatherData() {
//此时已经不需要我们自己定义存放观察者的数据结构了。
}
public void measurementsChanged() {
setChanged();
notifyObservers();// 调用notifyObservers方法之前,需先调用setChanged方法,来指示状态已经改变
/**
* Observable 类中notifyObservers代码如下: 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); } }
* 
* Observable 类中方法: 
* protected synchronized void setChanged() { changed = true; }
**/
}
public void setMeasurements(float temp, float humidity, float pressure) {
this.temp = temp;
this.humidity = humidity;
this.pressure = pressure;
measurementsChanged();
}
public float getTemp() {// 定义此方法,只因为观察者用它来“拉”数据
return temp;
}
public float getHumidity() {
return humidity;
}
public float getPressure() {
return pressure;
}
}
观察者的定义如下:
public class CurrentConditionDisplay implements Observer, DisplayElement {
private Observable weatherData;
private float temp;
private float humidity;
public CurrentConditionDisplay(Observable weatherData) {
this.weatherData = weatherData;
weatherData.addObserver(this);
}
@Override
public void display() {
System.out.println("CurrentConditionDisplay   temp = " + temp
+ " humidity = " + humidity);
}
@Override
public void update(Observable obs, Object arg) {
if (obs instanceof WeatherData) {
WeatherData weatherData = (WeatherData) obs;
this.temp = weatherData.getTemp();
this.humidity = weatherData.getHumidity();
display();
}
}
}


其余代码与上面自己定义接口的类似。


应用场景和优缺点

       上面已经对观察者模式做了比较详细的介绍,还是那句话,人无完人,模式也不是万能的,我们要用好设计模式来解决我们的实际问题,就必须熟知模式的应用场景和优缺点:

       观察者模式的应用场景:

1、  对一个对象状态的更新,需要其他对象同步更新,而且其他对象的数量动态可变。

2、  对象仅需要将自己的更新通知给其他对象而不需要知道其他对象的细节。

观察者模式的优点:

1、  Subject和Observer之间是松偶合的,分别可以各自独立改变。

2、  Subject在发送广播通知的时候,无须指定具体的Observer,Observer可以自己决定是否要订阅Subject的通知。

3、  遵守大部分GRASP原则和常用设计原则,高内聚、低偶合。

观察者模式的缺陷:

1、  松偶合导致代码关系不明显,有时可能难以理解。(废话)

2、  如果一个Subject被大量Observer订阅的话,在广播通知的时候可能会有效率问题。(毕竟只是简单的遍历)


参考:http://www.cnblogs.com/justinw/archive/2007/05/02/734522.html#!comments


相关文章

PHP速学视频免费教程(入门到精通)
PHP速学视频免费教程(入门到精通)

PHP怎么学习?PHP怎么入门?PHP在哪学?PHP怎么学才快?不用担心,这里为大家提供了PHP速学教程(入门到精通),有需要的小伙伴保存下载就能学习啦!

下载

相关标签:

本站声明:本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系admin@php.cn

相关专题

更多
Uvicorn 报错与问题解决教程
Uvicorn 报错与问题解决教程

本专题汇总了Uvicorn服务器常见问题与报错解决的教程合集,涵盖启动失败、端口占用、模块导入错误、事件循环冲突及热重载失效等典型故障的排查思路。同时整理了多进程与Worker配置、SSL/HTTPS证书报错、跨域与代理配置等实战解决方案,助你快速定位并解决Uvicorn运行中的各类问题。

2026.11.02

0

14

Uvicorn使用教程
Uvicorn使用教程

本专题汇总了Uvicorn使用教程与启动运行指南,涵盖其作为Python ASGI服务器的定位与特点,以及命令行启动(uvicorn main:app --reload)、参数配置(host/port/workers)、程序内嵌运行与Gunicorn结合部署等核心用法。同时整理了热重载调试、日志配置与生产环境部署优化等技巧,助你快速掌握Uvicorn的运行与配置。

2026.11.02

0

17

Uvicorn安装配置教程
Uvicorn安装配置教程

本专题汇总了Uvicorn的安装配置教程与部署指南,涵盖通过pip安装Uvicorn、启动FastAPI/Starlette应用、开发模式热重载(--reload)及多进程工作模式配置等核心操作。同时整理了结合Gunicorn的生产部署方案、HTTPS与反向代理配置及性能调优技巧,助你快速完成Uvicorn服务的安装与上线部署。

2026.11.02

0

17

Vite 项目如何打包和部署
Vite 项目如何打包和部署

Vite项目打包部署专题汇集生产构建、产物预览、输出目录、资源路径、Nginx与GitHub Pages部署及常见白屏和路由404问题的已审核文章,帮助读者完成上线流程。

2026.10.28

100

16

Vite 如何搭配 Vue.js
Vite 如何搭配 Vue.js

Vite搭配Vue.js专题汇集创建Vue 3项目、配置开发环境、路径别名、Vue插件、代理、多环境构建与调试的已审核文章,帮助读者完成从初始化到日常开发的配置。

2026.10.28

80

15

Vite 项目目录怎么看
Vite 项目目录怎么看

Vite项目目录专题汇集入口文件、src源码、public静态资源、路径别名、项目根目录和构建输出目录相关的已审核文章,帮助读者理解各文件夹的用途与引用方式。

2026.10.28

240

10

Gradle插件开发入门教程
Gradle插件开发入门教程

本专题介绍Gradle插件开发的基本流程,涵盖插件类编写、自定义任务注册、扩展参数设计和插件应用方法,帮助你理解插件如何组织构建逻辑,减少多个项目之间的重复配置。

2026.10.22

140

11

Gradle Wrapper配置教程
Gradle Wrapper配置教程

本专题介绍Gradle Wrapper的基本作用,涵盖文件组成、生成方法、版本配置和任务调用,帮助你理解项目如何使用指定的Gradle版本,减少不同开发环境带来的构建差异。

2026.10.22

140

10

Golangci-lint检查器使用教程
Golangci-lint检查器使用教程

本专题整理Golangci-lint常用Linters的用途与配置方法,覆盖错误处理、无效代码、安全、复杂度、命名和依赖检查,帮助项目合理选规则。

2026.10.22

100

15

热门下载

更多
网站特效
/
网站源码
/
网站素材
/
前端模板

精品课程

更多
相关推荐
/
热门推荐
/
最新课程
Revel 官方 app.conf 文档
Revel 官方 app.conf 文档

共0课时 | 0人学习

Conan 2 入门教程
Conan 2 入门教程

共0课时 | 0人学习