热门标签 | HotTags
当前位置:  开发笔记 > 编程语言 > 正文

HeadFirst设计模式(二)观察者模式

为什么80%的码农都做不了架构师?HeadFirst设计模式(二)-观察者模式有趣的事情发生时,可千万别错过了

为什么80%的码农都做不了架构师?>>>   hot3.png

HeadFirst设计模式(二) - 观察者模式

 

有趣的事情发生时,可千万别错过了!

    观察者模式是JDK中使用最多的模式之一,非常有用。我们也会一并介绍一对多关系,以及松耦合。有个观察者,你将会消息灵通。

工作合约

    某软件公司接到一个工作合约,内容如下:

 

    恭喜贵公司获选为敝公司(Weather-O-Rama气象站)建立下一代Internate气象站!

    该气象站必须建立在我们专利申请中的WeatherData对象上,由WeatherData负责监测目前的天气情况(温度、湿度和气压等)。我们希望贵公司建立一个应用,有三种布告板,分别显示目前的情况,气象统计及简单的预报。当WeatherData对象获得最新的监测数据时,三种布告板实时更新。

    而且,这是一个可以拓展的气象站,Weather-O-Rama气象站希望发布一组API,好让其他开发人员可以写出自己的气象布告板,并插入次应用中。我们希望贵公司提供这样的API。

气象监测站应用的概况

    此系统中的三个部分是气象站(获取实际气象数据的物理装置)、WeatherData对象(最终来自气象站的数据)和布告板(显示目前天气状况给用户看)。如图:

000159_p9pP_2450666.png

    WeatherData对象知道如何跟物理气象站联系,以获得监控的实施气象数据。WeatherData对象会随即更新三个布告板的显示:目前状况(温度、湿度、气压)、气象统计和天气预报。

    我们的工作就是建立一个应用,利用WeatherData对象获得数据,并更新三个布告板。    

    瞧一瞧客户给提供的WeatherData类吧。

000832_VTwI_2450666.png

    我们的工作是实现measurementsChanged()方法,好让它更新目前的状况、气象统计和天气预报的显示布告板。

我们目前知道什么?

    Weather-O-Rama气象站的要求说明并不是很清楚,我们必须搞懂该做些什么,那么,我们目前知道些什么呢?

  • WeatherData类具有getter方法,可以取得三个测量值:温度、湿度与气压。

  • 当新的测量数据准备好时,measurementsChanged()方法就会被调用(我们不在乎次方法是如何被调用的,我们只在乎它被调用了,也许被某个其他的程序调用的,比如硬件采集,这是Weather-O-Rama气象站提供的WeatherData类已经实现的功能)。

  • 我们需要实现三个使用天气数据的布告板,一旦WeatherData有新的测量数据就马上更新。

  • 次系统必须可拓展,让其他开发人员自定义布告板。

先看一个错误的示范

    如果不进行思考,直接进入开发阶段,只为了完成工作而工作,那么这是第一个可能的实现。我们依照Weather-O-Rama气象站开发人员的暗示,在客户提供的类的measurementsChanged()方法中添加我们的代码:

package cn.net.bysoft.observer;/*** 这是一个没有经过任何思考而写的代码。* */
public class WeatherData {// 该方法在硬件有最新监测数据时被调用。public void measurementsChanged() {//    该类中的代码又我们公司实现。// 调用三个getter方法获得最新的监测数据。float temp = getTemperature();float humidity = getHumidity();float pressure = getPressure();// 更新三个我们公司自己开发的布告板类。currentConditionsDispaly.update(temp, humidity, pressure);statisticsDisplay.update(temp, humidity, pressure);forecastDispaly.update(temp, humidity, pressure);}public float getTemperature() {return temperature;}public float getHumidity() {return humidity;}public float getPressure() {return pressure;}// 这三个setter方法可能会在硬件监测到最新的气象数据时被赋值。// 然后硬件会调用measurementsChanged()方法。public void setTemperature(float temperature) {this.temperature = temperature;}public void setHumidity(float humidity) {this.humidity = humidity;}public void setPressure(float pressure) {this.pressure = pressure;}private float temperature;private float humidity;private float pressure;
}

    在我们的第一个实现中,存在一下的缺点:

  • 我们是针对具体实现编程,而非针对接口;

  • 对于每个新的布告版,我们都得修改代码;

  • 我们无法在运行时动态地添加或删除布告板;

  • 我们尚未封装改变的部分;

我们的实现有什么不对?

    针对具体实现编程,会导致我们以后在添加或删除布告板时必须修改程序。而且,布告板的update()方法看起来是一个统一的接口,布告板的方法名称都是update(),参数都是温度、湿度和气压。

    现在就来看看观察者模式,然后再回来看看如何将此模式应用到气象观测站。

    观察者就好像订阅报纸或者杂志。

  1. 报社的业务就是出版报纸。

  2. 向某家报社订阅报纸,只要他们有新报纸出版,就会给你送来。只要你是他们的订户,你就会一直收到新报纸。

  3. 当你不想再看报纸的时候,取消订阅,他们就不会再送新报纸来。

  4. 只要报社还在运营,就会一直有人向他们订阅报纸或取消订阅。

    订阅者+出版者=观察者模式,只是名称不太一样。

    出版者改为主题(Subject),订阅者改为观察者(Observer),一个主题对应多个观察者,是一个一对多的关系。来看一下观察者的类图:

213226_Lc2B_2450666.png

    这里有一个设计原则:

设计原则:

为了交互对象之间的松耦合设计而努力。

松耦合的威力

    当两个对象之间松耦合,他们依然可以交互,但是不太清楚彼此的细节。

    观察者模式提供了一种对象设计,让主题和观察者之间松耦合。

    关于观察者的一切,主题值知道观察者实现了某个接口。主题不需要知道观察者的具体类是谁,做了些什么或其他任何细节。

    任何时候我们都可以添加新的观察者。因为主题唯一依赖的东西是一个实现Observer接口的对象列表,所以我们可以随时添加观察者。事实上,在运行时我们可以用新的观察者取代现有的观察者,主题不会受到任何影响。同样的,也可以在任何时候删除某些观察者。

    有新类型出现的观察者出现时,主题的代码不需要修改。我们可以独立地复用主题或观察者。如果我们在其他任何地方需要使用主题或观察者,可以轻易地复用,因为二者并非紧耦合,而是松耦合。

    松耦合的设计之所以能让我们建立有弹性的OO系统,能够应对变化,是因为对象之间的互相依赖降到了最低。

设计与实现气象站

    了解了这么多,开始设计与实现气象站吧。

215055_9Tjw_2450666.png

    设计类图如上图所示,让我们从建立接口开始吧!

package cn.net.bysoft.observer;/*** 布告板显示信息接口。* */
public interface DisplayElement {/*** 当布告板需要显示时,调用次方法。* */public void display();
}

package cn.net.bysoft.observer;/*** 观察者接口。* */
public interface Observer {/*** 当气象监测数据改变时,主题会把这些状态值当作方法的参数,传送给观察者。* */public void update(float temp, float humidity, float perssure);
}

package cn.net.bysoft.observer;/*** 主题接口。* */
public interface Subject {/*** registerObserver和removeObserver方法都需要一个观察者作为变量,该观察者是用来注册或被删除的。* */public void registerObserver(Observer ob);public void removeObserver(Observer ob);/*** 当主题的状态改变时,这个方法会被调用,已通知所有的观察者。* */public void notifyObservers();
}

    接下来,我们要用观察者模式实现WeatherData类了。

package cn.net.bysoft.observer;import java.util.ArrayList;public class WeatherData implements Subject {public WeatherData() {observers = new ArrayList();}/*** 注册观察者。* */public void registerObserver(Observer ob) {observers.add(ob);}/*** 移除观察者。* */public void removeObserver(Observer ob) {int index = observers.indexOf(ob);if (index >= 0)observers.remove(index);}/*** 当有最新的气象监测数据时被调用。↓下面的函数调用。* */public void notifyObservers() {for (Observer ob : observers) {ob.update(this.temperature, this.himidity, this.pressure);}}/*** 气象站硬件监测到新数据会调用该方法。↓下面的函数调用。* */public void measurementsChanged() {notifyObservers();}/*** 因为没有气象站,所以模拟一个硬件,获得气象数据调用measurementsChanged()方法。* 在main()函数中使用。* */public void setMeasurements(float temperature, float himidity, float pressure) {this.temperature = temperature;this.himidity = himidity;this.pressure = pressure;measurementsChanged();}public ArrayList getObservers() {return observers;}public void setObservers(ArrayList observers) {this.observers = observers;}public float getTemperature() {return temperature;}public void setTemperature(float temperature) {this.temperature = temperature;}public float getHimidity() {return himidity;}public void setHimidity(float himidity) {this.himidity = himidity;}public float getPressure() {return pressure;}public void setPressure(float pressure) {this.pressure = pressure;}private ArrayList observers;private float temperature;private float himidity;private float pressure;
}

最后,编写布告板吧!

    我们已经把WeatherData类写出来了,现在轮到布告板了。Weather-O-Rama气象站订购了三个布告板,首先来实现其中一个布告板。

package cn.net.bysoft.observer;public class CurrentConditionsDisplay implements Observer, DisplayElement {public CurrentConditionsDisplay() {}public CurrentConditionsDisplay(Subject weatherData) {//    实例化布告板的时候,传入一个主题,将自己注册到主题中。this.weatherData = weatherData;weatherData.registerObserver(this);}public void display() {System.out.println("当前conditions:" + temperature + "F degrees and "+ himidity + "% humidity");}public void update(float temp, float humidity, float perssure) {this.temperature = temp;this.himidity = humidity;// 更新数据后刷新布告板。display();}private float temperature;private float himidity;//    要观察的主题。private Subject weatherData;
}

    布告板已经实现完毕,编写一个main()方法测试一下观察者模式吧!

package cn.net.bysoft.observer;public class Main {public static void main(String[] args) {// 首先,创建一个主题。// 这里应该使用Subject weatherData = new WeatherData();来创建主题。// 但是我们在WeatherData中添加了一个模拟硬件发送气象数据的函数,Subject中并没有这个函数。// 所以使用了WeatherData来创建主题。WeatherData weatherData = new WeatherData();// 建立一个观察者(也就是布告板),把主题传给观察者,在观察者的构造函数中订阅主题。Observer ccDisplay = new CurrentConditionsDisplay(weatherData);// 模拟一个气象变化的情况。weatherData.setMeasurements(80, 65, 30.4f);System.out.println("\n====================\n");// 又变化了!weatherData.setMeasurements(80, 65, 30.4f);/*** output: * 当前conditions:80.0F degrees and 65.0% humidity* * ====================* * 当前conditions:80.0F degrees and 65.0% humidity* * */}}

    另外两个布告板如上代码,另外,Java中提供了内置的观察者模式接口,它们是:

  • java.util.Observable

  • java.util.Observer

    想要进一步了解可以网上查找资料,或者和我一样购买HeadFirst设计模式这本书,这本书真的很不错。

    以上就是观察者模式。

 


转:https://my.oschina.net/zz006/blog/2048674



推荐阅读
  • Go Cobra命令行工具入门教程
    本文介绍了Go语言实现的命令行工具Cobra的基本概念、安装方法和入门实践。Cobra被广泛应用于各种项目中,如Kubernetes、Hugo和Github CLI等。通过使用Cobra,我们可以快速创建命令行工具,适用于写测试脚本和各种服务的Admin CLI。文章还通过一个简单的demo演示了Cobra的使用方法。 ... [详细]
  • 本文介绍了解决Netty拆包粘包问题的一种方法——使用特殊结束符。在通讯过程中,客户端和服务器协商定义一个特殊的分隔符号,只要没有发送分隔符号,就代表一条数据没有结束。文章还提供了服务端的示例代码。 ... [详细]
  • Centos7.6安装Gitlab教程及注意事项
    本文介绍了在Centos7.6系统下安装Gitlab的详细教程,并提供了一些注意事项。教程包括查看系统版本、安装必要的软件包、配置防火墙等步骤。同时,还强调了使用阿里云服务器时的特殊配置需求,以及建议至少4GB的可用RAM来运行GitLab。 ... [详细]
  • Java容器中的compareto方法排序原理解析
    本文从源码解析Java容器中的compareto方法的排序原理,讲解了在使用数组存储数据时的限制以及存储效率的问题。同时提到了Redis的五大数据结构和list、set等知识点,回忆了作者大学时代的Java学习经历。文章以作者做的思维导图作为目录,展示了整个讲解过程。 ... [详细]
  • javascript  – 概述在Firefox上无法正常工作
    我试图提出一些自定义大纲,以达到一些Web可访问性建议.但我不能用Firefox制作.这就是它在Chrome上的外观:而那个图标实际上是一个锚点.在Firefox上,它只概述了整个 ... [详细]
  • t-io 2.0.0发布-法网天眼第一版的回顾和更新说明
    本文回顾了t-io 1.x版本的工程结构和性能数据,并介绍了t-io在码云上的成绩和用户反馈。同时,还提到了@openSeLi同学发布的t-io 30W长连接并发压力测试报告。最后,详细介绍了t-io 2.0.0版本的更新内容,包括更简洁的使用方式和内置的httpsession功能。 ... [详细]
  • 本文介绍了Hyperledger Fabric外部链码构建与运行的相关知识,包括在Hyperledger Fabric 2.0版本之前链码构建和运行的困难性,外部构建模式的实现原理以及外部构建和运行API的使用方法。通过本文的介绍,读者可以了解到如何利用外部构建和运行的方式来实现链码的构建和运行,并且不再受限于特定的语言和部署环境。 ... [详细]
  • 本文介绍了PhysioNet网站提供的生理信号处理工具箱WFDB Toolbox for Matlab的安装和使用方法。通过下载并添加到Matlab路径中或直接在Matlab中输入相关内容,即可完成安装。该工具箱提供了一系列函数,可以方便地处理生理信号数据。详细的安装和使用方法可以参考本文内容。 ... [详细]
  • XML介绍与使用的概述及标签规则
    本文介绍了XML的基本概念和用途,包括XML的可扩展性和标签的自定义特性。同时还详细解释了XML标签的规则,包括标签的尖括号和合法标识符的组成,标签必须成对出现的原则以及特殊标签的使用方法。通过本文的阅读,读者可以对XML的基本知识有一个全面的了解。 ... [详细]
  • 计算机存储系统的层次结构及其优势
    本文介绍了计算机存储系统的层次结构,包括高速缓存、主存储器和辅助存储器三个层次。通过分层存储数据可以提高程序的执行效率。计算机存储系统的层次结构将各种不同存储容量、存取速度和价格的存储器有机组合成整体,形成可寻址存储空间比主存储器空间大得多的存储整体。由于辅助存储器容量大、价格低,使得整体存储系统的平均价格降低。同时,高速缓存的存取速度可以和CPU的工作速度相匹配,进一步提高程序执行效率。 ... [详细]
  • 本文介绍了计算机网络的定义和通信流程,包括客户端编译文件、二进制转换、三层路由设备等。同时,还介绍了计算机网络中常用的关键词,如MAC地址和IP地址。 ... [详细]
  • 关键词:Golang, Cookie, 跟踪位置, net/http/cookiejar, package main, golang.org/x/net/publicsuffix, io/ioutil, log, net/http, net/http/cookiejar ... [详细]
  • Python瓦片图下载、合并、绘图、标记的代码示例
    本文提供了Python瓦片图下载、合并、绘图、标记的代码示例,包括下载代码、多线程下载、图像处理等功能。通过参考geoserver,使用PIL、cv2、numpy、gdal、osr等库实现了瓦片图的下载、合并、绘图和标记功能。代码示例详细介绍了各个功能的实现方法,供读者参考使用。 ... [详细]
  • 本文介绍了Android 7的学习笔记总结,包括最新的移动架构视频、大厂安卓面试真题和项目实战源码讲义。同时还分享了开源的完整内容,并提醒读者在使用FileProvider适配时要注意不同模块的AndroidManfiest.xml中配置的xml文件名必须不同,否则会出现问题。 ... [详细]
  • Go GUIlxn/walk 学习3.菜单栏和工具栏的具体实现
    本文介绍了使用Go语言的GUI库lxn/walk实现菜单栏和工具栏的具体方法,包括消息窗口的产生、文件放置动作响应和提示框的应用。部分代码来自上一篇博客和lxn/walk官方示例。文章提供了学习GUI开发的实际案例和代码示例。 ... [详细]
author-avatar
用户uuexwjx90j
这个家伙很懒,什么也没留下!
PHP1.CN | 中国最专业的PHP中文社区 | DevBox开发工具箱 | json解析格式化 |PHP资讯 | PHP教程 | 数据库技术 | 服务器技术 | 前端开发技术 | PHP框架 | 开发工具 | 在线工具
Copyright © 1998 - 2020 PHP1.CN. All Rights Reserved | 京公网安备 11010802041100号 | 京ICP备19059560号-4 | PHP1.CN 第一PHP社区 版权所有