《Head First设计模式》第二章笔记 观察者模式

背景

客户有一个WeatherData对象,负责追踪温度、湿度和气压等数据。现在客户给我们提了个需求,让我们利用WeatherData对象取得数据,并更新三个布告板:目前状况、气象统计和天气预报。
WeatherData对象提供了4个接口:
getTemperature():获取温度
getHumidity():获取湿度
getPressure():获取气压
measurementsChanged():一旦气象测量更新,此方法会被调用

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

我们的工作就是建立一个应用,利用WeatherData对象取得数据,并更新三个布告板:目前状况、气象统计和天气预报。

先看一个错误示范

1

2

3

4

5

6

7

8

9

10

11

12

13

14

public class WeatherData{

    //实例变量声明

    public void measurementsChanged(){

        //获取温度、湿度、气压数据

        float temp=getTemperature();

        float humidity=getHumidity();

        float pressure=getPressure(); 

        //调用update()更新布告板

        currentConditionsDisplay.update(temp,humidity,pressure);

        statisticsDisplay.update(temp,humidity,pressure);

        forecastDisplay.update(temp,humidity,pressure);

}

    //其他WeatherData方法   

}

回顾第一章中的概念与原则

1

2

3

currentConditionsDisplay.update(temp,humidity,pressure);

statisticsDisplay.update(temp,humidity,pressure);

forecastDisplay.update(temp,humidity,pressure);

针对具体实现编程会导致以后增删布告版时必须修改程序,

对变化的部分必须封装起来。

认识观察者模式

先看看报纸和杂志的订阅是怎么回事:

  1. 报社的业务就是出版报纸;
  2. 向某家报社订阅报纸,只要他们有新报纸出版,就会给你送来。只要你是他们的订户,你就会一直收到新报纸;
  3. 当你不想再看报纸的时候,取消订阅,他们就不会再送新报来;
  4. 只要报社还在运营,就会有人向他们订阅报纸或取消订阅报纸。

观察者模式与订阅报纸类似,出版者改称为“主题”(Subject),订阅者改称为“观察者”(Observer)。主题对象管理某些数据。当主题内的数据改变,就会通知观察者。已经订阅了主题的观察者会在主题数据发送改变时收到通知。如果观察者不想接收新的数据,可以取消订阅,之后主题数据改变时就不会收到通知。

定义观察者模式

观察者模式定义了对象之间的一对多依赖,这样一来,当一个对象改变时,他的所有依赖者都会收到通知并自动更新。

类图

主题与观察者之间松耦合

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

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

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

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

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

改变主题或观察者其中一方,并不会影响另一方。因为两者是松耦合的,所以只要他们之间的接口仍被遵守,我们就可以自由地改变他们。

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

 

设计气象站

思考:我们把WeatherData对象当作主题,把布告板当作观察者,布告板为了取得信息,就必须先向WeatherData对象注册。

我们必须记得,每个布告板都有差异,这也就是为什么我们需要一个共同的接口的原因。尽管布告板的类都不一样,但是它们都应该实现相同的接口,好让WeathcrData对象能够知道如何把观测值送给它们。所以每个布告板都应该有一个大概名为update()的方法,以供WeatherData对象调用,而这个update)方法应该在所有布告板都实现的共同接口里定义。

类图

实现(一)

java为观察者模式提供了内置支持。但是,我们暂时不用它,而是先自己动手。虽然,某些时候可以利用Java内置的支持,但是有许多时候,自己建立这一切会更具弹性(况且建立这一切并不是很麻烦)。
所以,让我们从建立接口开始吧:

Subject接口

1

2

3

4

5

6

7

public interface Subject {

   //这两个方法都需要一个观察者作为变量,该观察者是用来注册或者被删除的

   public void registerObserver(Observer o);

   public void removeObserver(Observer o);

   //主题状态改变时,此方法被调用,以通知所有的观察者

   public void notifyObservers();

}

Observer接口

1

2

3

4

5

public interface Observer {

     //当气象观测值改变时,主题会把这些状态值当作方法参数传递给观察者

     //所有方法都必须实现update()方法,以实现观察者接口

     public void update(float temp ,float humidity,float pressure);

}

DisplayElement接口

1

2

3

4

public interface DisplayElement {

     //当布告板需要显示时调用此方法

     public void display();

}

在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

public class WeatherData implements Subject {

     //ArrayList用于记录观察者

     private ArrayList observers;

     private float temperature;

     private float humidity;

     private float pressure;

     public WeatherData() {

         // 在构造方法中建立ArrayList                         

         observers=new ArrayList();

     }

     @Override

     public void registerObserver(Observer o) {

         // 当注册注册观察者时,将它加到ArrayList后面即可

         observers.add(o);

     }

     @Override

     public void removeObserver(Observer o) {

         // 当观察者取消订阅时,则将它从Arraylist中删除

        int i=observers.indexOf(o);

        if(i>=0)

              observers.remove(o);

     }

     @Override

     public void notifyObservers() {

         // 将状态告诉每一个观察者,因为每个观察者都实现了update()方法

         for(int i=0;i<observers.size;i++){

             Observer observer=(Observer)observers.get(i);

             observer.update(temperature, humidity, pressure);

         }

     }

     //当数据更新时,通知观察者

     public void measurementsChanged() {

         notifyObservers();

     }

     public void setMeasurements(float temperature,float humidity,float presure) {

         //测试用方法

         this.temperature=temperature;

         this.humidity=humidity;

         this.pressure=presure;

         measurementsChanged();

     }

}

布告板建立

此处只展示“目前状况”布告板,另外两个与此类似。

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

20

public class CurrentConditionsDisplay implements Observer, DisplayElement {

     private float temperature;

     private float humidity;

     private Subject weatherData;

     public CurrentConditionsDisplay(Subject weatherData) {

         this.weatherData=weatherData;

         weatherData.registerObserver(this);

     }

     @Override

     public void display() {

         //显示当前温湿度状况

         System. out. println("Current conditions:"+ temperature+"F degrees and"t humidity +"% humidity");

     }

     @Override

     public void update(float temperature, float humidity, float pressure) {

         this.temperature=temperature;

         this.humidity=humidity;

         display();

     }

}

测试类

1

2

3

4

5

6

7

8

public static void main(String[] args) {

     // TODO 自动生成的方法存根

     WeatherData weatherData=new WeatherData();

     CurrentConditionsDisplay currentiDisplay=new CurrentConditionsDisplay(weatherData);

     weatherData.setMeasurements(806530.4f);

     weatherData.setMeasurements(827529.2f);

     weatherData.setMeasurements(789029.2f);

 }

输出

这种观察者模式总是在数据改变时自动推送全部数据,而观察者没有主动获取数据的方法,因此有时会让观察者很困扰,总是收到一大堆数据而观察者想要的只是其中一个或两个而已。但让观察者自己去取得数据就必须开放权限,这样又带来数据安全性问题,或者使用getter方法又会让需要很多数据的观察者多次调用才能全部取得想要的数据。

主动推送与观察者自行获取都有各自的优缺点,因此Java内置的Observer模式两种方法都支持!

java.util包(package)内包含最基本的Observer接口与Observable类,这和我们的Subject接口与0bserver接口很相似。
Observer接口与0bscrvable类使用上更方便,因为许多功能都已经事先准备好了。你甚至可以使用推(push)或拉(pull)的方式传送数据。

使用Java内置观察者模式类图

类图大体和原来差不多,主题接口变为了Observerable类,WeatherData也不再提供addObserver()等方法而是继承自Observerable.

如何把对象变成观察者…

如同以前一样,实现观察者接口(java.uitl.Observer),然后调用任何Observable对象的addObserver)方法。不想再当观察者时,调用deleteObserver()方法就可以了。

可观察者要如何送出通知……

首先,你需要利用扩展java.util.Observable接口产生“可观察者”类,然后,需个步骤:
①先调用setChanged()方法,标记状态已经改变的事实。
②然后调用两种notifyObservers0方法中的一个:
notifyobservers()或 notifyobservers(object arg)

观察者如何接收通知……

同以前一样,观察者实现了更新的方法,但是方法的签名不太一样:

如果你想“推”(push)数据给观察者,你可以把数据当作数据对象传送给notifyObservers(arg)方法。否则,观察者就必须从可观察者对象中“拉”(pull)数据。

其中 setChanged()方法是用来标记状态是否改变的,好让notifyObservers()知道当它被调用时应该更新观察者。如果调用notifyObservers)之前没有先调用setChanged(),观察者就“不会”被通知。让我们看看Observable内部,以了解这一切:

这样做有其必要性。setChanged()方法可以让你在更新观察者时,有更多的弹性,你可以更适当地通知观察者。

利用内置的支持重做气象站

首先,把WeatherData改成使用java.util.Observable

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

import java.util.Observable;//导入Observerable

import java.util.Observer;

public class WeatherData extends Observable {

    private float temperature;

    private float humidity;

    private float pressure;

    public WeatherData(){}//不再需要为了记住观察者们而建立数据结构了

    public void measurementsChanged(){

        setChanged();

        notifyObservers();//我们没有调用 notifyObsevets()传送数据对象,这表示我们采用的做法是“拉”      

    }

    public void setMeasurements(float temperature,float humidity,float pressure){

        this.temperature=temperature;

        this.humidity=humidity;

        this.pressure=pressure;

        measurementsChanged();

    }

    public float getTemperature(){

        return temperature;

    }

    public float getHumidity(){

        return humidity;

    }

    public float getPressure(){

        return pressure;

    }

}

重做CurrentConditionsDisplay

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

import java.util.Observable;//导入Observerable

import java.util.Observer;

//实现java.util.Observer接口

public class CurrentConditionsDisplay implements Observer, DisplayElement {

     Observerable observable;

     private float temperature;

     private float humidity;

     public CurrentConditionsDisplay(Observable observable) {

         // 将Observer当参数,并将CurrentConditionsDisplay对象登记为观察者

         this.observable=observable;

         observable.addObserver(this);

     }

     @Override

     public void display() {

         //显示当前温湿度状况

         System. out. println("Current conditions:"+ temperature+"F degrees and"t humidity +"% humidity");

     }

     @Override

     public void update(Observable obs, Object arg) {

         // TODO 自动生成的方法存根

         if(obs instanceof WeatherData){

             WeatherData weatherData=(WeatherData)obs;

             this.temperature=weatherData.getTemperature();

             this.humidity=weatherData.getHumidity();

             display();

         }

    }

}

输出

嗯!你注意到差别了吗?再看一次……

你会看到相同的计算结果,但是奇怪的地方在于,文字输出的次序不一样。怎么会这样呢?

思考一下.....

...

...

...

...

...

...

...

不要依赖于观察者被通知的次序

java.uitl.Observable实现了它的notifyObservers()方法,这导致了通知观察者的次序不同于我们先前的次序。谁也没有错,只是双方选择不同的方式实现罢了。但是可以确定的是,如果我们的代码依赖这样的次序,就是错的。为什么呢?因为一旦观察者/可观察者的实现有所改变,通知次序就会改变,很可能就会产生错误的结果。这绝对不是我们所认为的松耦合。

java.util.Observable的弊端

如同你所发现的,可观察者是一个“类”而不是一个“接口”,更糟的是,它甚没有实现一个接口。不幸的是,java.util.Observable的实现有许多问题,限制了它的使用和复用。这并不是说它没有提供有用的功能,只是想提醒大家注意一些事实。

Observable是一个,类到底会造成什么问题

首先,因为Observable是一个“类”,你必须设计一个类继承它。如果某类想同时具有Observable类和另一个超类的行为,就会陷入两难,毕竟Java不支持多重继承。
这限制了Observable的复用潜力(而增加复用潜力不正是我们使用模式最原始的动机吗?)。

再者,因为没有Observable接口,所以你无法建立自己的实现,和Java内置的Observer API搭配使用,也无法将java.util的实现换成另一套做法的实现(比方说,Observable将关键的方法保护起来如果你看看ObservableAP1,你会发现setChanged)方法被保护起来了(被定义成protected)。那又怎么样呢?这意味着:除非你继承自Observable,否则你无法创建Observable实例并组合到你自己的对象中来。这个设计违反了第二个设计原则:“多用组合,少用继承”。

如果你能够扩展java.util.Observable,那么Observable“可能”可以符合你的需求。否则,你可能需要像本章开头的做法那样自己实现这一整套观察者模式。

在JDK中,并非只有在java.util中才能找到观察者模式,共实在JavaBeans和Swing中,也都实现了观察者模式。

结束

具体实现的代码,我觉得还是看这本书来的清晰,在本章节末尾,还说到"推模式"和"拉模式",所谓"推模式"就是主题对象传递数据,

"拉模式"就是观察者对象主动得数据,我们要牢记一些设计原则

封装变化
多用组合,少用继承
为交互对象之间的松耦合设计而努力

 

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/445430.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

《Head First设计模式》第三章笔记 装饰者模式

装饰者模式&#xff08;Decorator Pattern) *利用组合&#xff08;composition&#xff09;和委托&#xff08;delegation&#xff09;可以在运行时实现继承行为的效果&#xff0c;动态地给对象加上新的行为。 *利用继承扩展子类的行为&#xff0c;是在编译时静态决定的&#x…

机器学习中如何解决数据不平衡问题?

文章目录目录什么是数据不平衡问题&#xff1f;数据不平衡会造成什么影响&#xff1f;如何处理数据不平衡问题&#xff1f;1、重新采样训练集1.1随机欠抽样1.2.基于聚类的过采样2.使用K-fold交叉验证3.转化为一分类问题4.组合不同的重采样数据集5.用不同比例重新采样6.多模型Ba…

《Head First设计模式》第四章笔记 工厂模式

之前我们一直在使用new操作符&#xff0c;但是实例化这种行为并不应该总是公开的进行&#xff0c;而且初始化经常会造成耦合问题&#xff0c;工厂模式将摆脱这种复杂的依赖&#xff0c;本次内容包括简单工厂&#xff0c;工厂方法和抽象工厂三种情况。 1 2 3 4 5 6 Duck duck&a…

《Head First设计模式》第五章笔记-单件模式

单件模式 定义&#xff1a;确保一个类只有一个实例&#xff0c;并提供全局访问点。 编写格式&#xff1a; 1 2 3 4 5 6 public class MyClass{ private MyClass(){}//构造方法私有化 public static MyClass getInstance(){ //提供全局访问点 return new My…

《Head First设计模式》第六章笔记-命令模式

封装调用-命令模式 命令模式可将“动作的请求者”从“动作的执行者”对象中解耦。 本篇中将不再描述书中所引入的“巴斯特家电自动化公司”的遥控器控制案例&#xff0c;而使用简单易懂的餐厅案例。 在开始之前&#xff0c;让我们通过一个现实中的例子来了解命令模式。 理解…

一文读懂机器学习库graphLab

文章目录目录什么是graphlab为什么使用graphlab?如何安装graphlab?graphlab的简单使用。目录 什么是graphlab GraphLab 是由CMU&#xff08;卡内基梅隆大学&#xff09;的Select 实验室在2010 年提出的一个基于图像处理模型的开源图计算框架&#xff0c;框架使用C语言开发实…

《Head First设计模式》第七章-适配器模式、外观模式

适配器模式 适配器模式是什么&#xff0c;你一定不难理解&#xff0c;因为现实中到处都是。比如说&#xff1a; 如果你需要在欧洲国家使用美国制造的笔记本电脑&#xff0c;你可能需要使用一个交流电的适配器…… 当你不想改变现有的代码&#xff0c;解决接口不适配问题&#…

《Head First设计模式》第八章笔记-模板方法模式

模板方法模式 之前所学习的模式都是围绕着封装进行&#xff0c;如对象创建、方法调用、复杂接口的封装等&#xff0c;这次的模板方法模式将深入封装算法块&#xff0c;好让子类可以在任何时候都将自己挂接进运算里。 模板方法定义&#xff1a;模板方法模式在一个方法中定义一…

机器学习基础-吴恩达-coursera-(第一周学习笔记)----Introduction and Linear Regression

课程网址&#xff1a;https://www.coursera.org/learn/machine-learning Week 1 —— Introduction and Linear Regression 目录 Week 1 Introduction and Linear Regression目录一 介绍1-1 机器学习概念及应用1-2 机器学习分类 二 单变量的线性回归2-1 假设函数hypothesis2…

常见8种机器学习算法总结

简介 机器学习算法太多了&#xff0c;分类、回归、聚类、推荐、图像识别领域等等&#xff0c;要想找到一个合适算法真的不容易&#xff0c;所以在实际应用中&#xff0c;我们一般都是采用启发式学习方式来实验。通常最开始我们都会选择大家普遍认同的算法&#xff0c;诸如SVM&a…

redis——数据结构(字典、链表、字符串)

1 字符串 redis并未使用传统的c语言字符串表示&#xff0c;它自己构建了一种简单的动态字符串抽象类型。 在redis里&#xff0c;c语言字符串只会作为字符串字面量出现&#xff0c;用在无需修改的地方。 当需要一个可以被修改的字符串时&#xff0c;redis就会使用自己实现的S…

Hotspot虚拟机的对象

创建 Step1:类加载检查 虚拟机遇到一条 new 指令时&#xff0c;首先将去检查这个指令的参数是否能在常量池中定位到这个类的符号引用&#xff0c;并且检查这个符号引用代表的类是否已被加载过、解析和初始化过。如果没有&#xff0c;那必须先执行相应的类加载过程。 Step2:分…

redis——数据结构(整数集合,压缩列表)

4、整数集合 整数集合&#xff08;intset&#xff09;是 Redis 用于保存整数值的集合抽象数据结构&#xff0c; 可以保存 int16_t 、 int32_t 、 int64_t 的整数值&#xff0c; 并且保证集合中不会出现重复元素。 实现较为简单&#xff1a; typedef struct intset {// 编码方…

机器学习知识总结系列- 知识图谱(0-0)

文章目录目录机器学习知识图谱目录 本系列的文章只是根据个人的习惯进行总结&#xff0c;可能结构与一些书籍上不太一样&#xff0c;开始的内容比较简单&#xff0c;会随着后续的深入&#xff0c;不断丰富和更新图谱&#xff0c;同时也期待有相同兴趣的朋友一起给我留言一起丰富…

跳表介绍和实现

想慢慢的给大家自然的引入跳表。 想想&#xff0c;我们 1&#xff09;在有序数列里搜索一个数 2&#xff09;或者把一个数插入到正确的位置 都怎么做&#xff1f; 很简单吧 对于第一个操作&#xff0c;我们可以一个一个比较&#xff0c;在数组中我们可以二分&#xff0c;这…

机器学习知识总结系列- 基本概念(1-0)

文章目录目录1. 机器学习的定义2. 机器学习的分类2.1根据是否在人类监督下进行训练监督学习非监督学习半监督学习强化学习2.2根据是否可以动态渐进的学习在线学习批量学习2.3根据是否在训练数据过程中进行模式识别实例学习基于模型的学习3. 机器学习中的一些常见名词4. 机器学习…

剑指offer(刷题21-30)--c++,Python版本

文章目录目录第 21题&#xff1a;解题思路&#xff1a;代码实现&#xff1a;cpython第22 题&#xff1a;解题思路&#xff1a;代码实现&#xff1a;cpython第23 题&#xff1a;解题思路&#xff1a;代码实现&#xff1a;cpython第24 题&#xff1a;解题思路&#xff1a;代码实现…

剑指offer(刷题41-50)--c++,Python版本

文章目录目录第41题&#xff1a;解题思路&#xff1a;代码实现&#xff1a;cpython第42题&#xff1a;解题思路&#xff1a;代码实现&#xff1a;cpython第43题&#xff1a;解题思路&#xff1a;代码实现&#xff1a;cpython第44题&#xff1a;解题思路&#xff1a;代码实现&am…

redis——持久化

因为redis是内存数据库&#xff0c;他把数据都存在内存里&#xff0c;所以要想办法实现持久化功能。 RDB RDB持久化可以手动执行&#xff0c;也可以配置定期执行&#xff0c;可以把某个时间的数据状态保存到RDB文件中&#xff0c;反之&#xff0c;我们可以用RDB文件还原数据库…

剑指offer(刷题51-60)--c++,Python版本

文章目录目录第51题&#xff1a;解题思路&#xff1a;代码实现&#xff1a;cpython第52题&#xff1a;解题思路&#xff1a;代码实现&#xff1a;cpython第53题&#xff1a;解题思路&#xff1a;代码实现&#xff1a;cpython第54题&#xff1a;解题思路&#xff1a;代码实现&am…