Head First 设计模式(2)---观察者(Observer)模式

本文参照《Head First 设计模式》,转载请注明出处java

对于整个系列,咱们按照这本书的设计逻辑,使用情景分析的方式来描述,而且穿插使用一些问题,总结的方式来说述。而且全部的开发源码,都会托管到github上。 项目地址:github.com/jixiang5200…git

前一章主要讲解了设计模式入门和最经常使用的一个模式-----策略模式,并结合Joe的鸭子模型进行分析,想要了解的朋友能够回去回看一下。 这里咱们将继续介绍一种能够帮助对象知悉现状,不会错过该对象感兴趣的事。甚至对象能够本身决定是都要继续接受通知。有过设计模式学习经验的人会脱口而出-----观察者模式。对的,接下来咱们将了解一个新的设计模式,也就是观察者模式。github

1.引言

最近你的团队获取了一个新的合约,须要负责创建一个Weather-O-Rama公司的下一代气象站----Internet气象观测站。 合约内容以下:编程

恭喜贵公司获选为敝公司创建下一代Internet气象观测站!该气象站必须创建在咱们专利申请的WeatherData对象上,由WeatherData对象负责追踪目前的天气情况(温度、湿度、气压)。咱们但愿贵公司能创建一个应用,有三种布告板,分别显示目前的情况、气象统计及简单的预报。当WeatherData对象获取到最新的测量数据时,三种布告板必须实时更新。 并且,这是一个能够拓展的气象站,Weather-O-Rama气象站但愿公布一组API,让其余开发人员能够写出本身的气象布告板,并插入此应用中咱们但愿贵公司能够提供这样的API。 Weather-O-Rama气象站有很好的商业运营模式:一旦客户上钩,他们使用每一个布告板都要付钱最好的部分就是,为了感谢贵公司创建此系统,咱们将以公司的认股权支付你。 咱们期待看到你的设计和应用的alpha版本。 附注:咱们正在通宵整理WeatherData源文件给大家。设计模式

1.1需求分析

根据开发的经验,咱们首先分析Weather-O-Rama公司的需求:api

  • 此系统有三个部分组成:气象站(获取实际的气象数据的物理组成),WeatherData对象(追踪来自气象站的数据,并更新布告板)和布告板(显示目前天气情况展现给用户)
  • 项目应用中,开发者须要利用WeatherData去实时获取气象数据,而且更新三个布告板:目前气象,气象统计和天气预报。
  • 系统必须具有很高的可拓展性,让其余的开发人员能够创建定制的布告板,用户能够为所欲为地添加或删除任何布告板。

咱们初始设计结构以下: bash

初始设计结构

1.2WeatherData类

次日,Weather-O-Rama公司发送过来WeatherData的源码,其结构以下图 网络

WeatherData数据结构

其中measurementsChanged()方法在气象测试更新时,被调用。数据结构

1.3错误的编码方式

首先,咱们从大部分不懂设计模式的开发者经常使用的设计方式开始。 根据Weather-O-Rama气象站开发人员的需求暗示,在measurementsChanged()方法中添加相关的代码:框架

public class WeatherData {
    private float temperature;//温度
    private float humidity;//湿度
    private float pressure;//气压
    
    private CurrentConditionsDisplay currentConditionsDisplay;//目前状态布告板
    private StatisticsDisplay statisticsDisplay;//统计布告板
    private ForecastDisplay forecastDisplay;//预测布告板
    
    public WeatherData(CurrentConditionsDisplay currentConditionsDisplay
            ,StatisticsDisplay statisticsDisplay
            ,ForecastDisplay forecastDisplay){
        this.currentConditionsDisplay=currentConditionsDisplay;
        this.statisticsDisplay=statisticsDisplay;
        this.forecastDisplay=forecastDisplay;
    }
    
    
    public float getTemperature() {
        return temperature;
    }
    
    public float getHumidity(){
        return humidity;
    }
    
    public float getPressure(){
        return pressure;
    }

    //实例变量声明
    public  void measurementsChanged(){
        //调用WeatherData的三个getter方法获取最近的测量值
        float temp=getTemperature();
        float humidity=getHumidity();
        float pressure=getPressure();
        
        currentConditionsDisplay.update(temp,humidity,pressure);
        statisticsDisplay.update(temp,humidity,pressure);
        forecastDisplay.update(temp,humidity,pressure);
    }
    
    //通知发生变化
    public void setMeasurements(float temperature,float humidity,float pressure){
        this.temperature=temperature;
        this.humidity=humidity;
        this.pressure=pressure;
        measurementsChanged();
    }
}
复制代码

回顾第一章的三个设计原则,咱们发现这里违反了几个原则

第一设计原则 找出应用中可能须要变化之处,把它们独立出来,不要和那些不须要变化的代码混合在一块儿。

第二设计原则 针对于接口编程,不针对实现编程

第三设计原则 多用组合,少用继承

在这里咱们使用了针对实现编程,而且没有将变化部分独立出来,这样会致使咱们之后在增长或删除布告板时必须修改应用程序。并且,最重要的是,咱们牺牲了可拓展性。

分析错误点
既然这里咱们提到了要使用观察者模式来解决问题,那么该如何下手。而且,什么是观察者模式?

2.观察者模式

2.1认识观察者模式

为了方便理解,咱们从平常生活中常遇到的情形来理解观察者模式,这里咱们使用生活常见的报纸和杂志订阅业务逻辑来理解:

  • 报社的业务在于出版报纸
  • 订阅报纸的用户,只要对应报社有新的报纸出版,就会给你送来
  • 当用户不想继续订阅报纸,能够直接取消订阅。那么以后就算有新的报纸出版,也不会送给对应用户了。
  • 只要报社一直存在,任何用户均可以自由订阅或取消订阅报纸

从上面的逻辑咱们分析出,这里由如下部分组成,报社,用户,订阅。将其抽象出来就i是:出版者,订阅者,订阅。这里观察者模式的雏形已经出来了。

出版者+订阅者=观察者模式

若是上面已经理解了报社报纸订阅的逻辑,也能够很快知道观察者模式是什么。只是在其中名称会有差别,前面提到的“出版者”咱们能够称为**“主题(Subject)”“被观察者(Observable)”(后一个更加经常使用),“订阅者”咱们称为“观察者(Observer)”**,这里咱们采用类UML的结构图来解释:

观察者模式结构图

2.2 观察者模式注册/取消注册

场景1: 某一天,鸭子对象以为本身的朋友都订阅了主题,本身也想称为一个观察者。因而告诉主题,它想当一个观察者。完成订阅后,鸭子也成为一个观察者了。

鸭子成为观察者后的结构图
这样当主题数据发生变化时,鸭子对象也能够获得通知了!!

场景2: 老鼠对象厌烦了天天都被主题烦,决定从观察者序列离开,因而它告诉主题它想离开观察者行列,主题将它从观察者中除名。

老鼠离开观察者后的结构图
以后主题数据发生变化时,不会再通知老鼠对象。

上面的两个情形分别对应了注册和取消注册,这也是观察者模式最重要的两个概念。注册后的对象咱们才能够称为观察者。观察者取消注册后也不能称为观察者。

2.3 观察者模式定义

经过报纸业务和对象订阅的例子,咱们能够勾勒出观察者模式的基本概念。

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

主题/被观察者和观察者之间定义了一对多的关系。观察者依赖于主题/被观察者。一旦主题/被观察者数据发生改变的时候,观察者就会收到通知。那么,如何实现观察者和主题/被观察者呢?

2.4 观察者模式实现

因为网络上的实现观察者的方式很是多,咱们这里采起比较容易理解的方式Subject和Observer。对于更高级的使用方式,能够百度。 接下来咱们来看看基于Subject和Observer的类图结构:

Subject和Observer的类图结构

3. 设计气象站

到这里咱们再回到当初的问题,气象站中结构模型为一对多模型,其中WeatherData为气象模型中的“一”,而“多”也就对应了这里用来展现天气监测数据的各类布告板。相对于以前的针对实现的方式,使用观察者模式来设计会更加符合需求。优先咱们给出新的气象站模型。

气象站数据模型

3.1实现气象站

依照前面的设计结构图,最终来实现具体代码结构

1.Subject

public interface Subject {

    //注册观察者
    public void registerObserver(Observer o);
    
    //删除观察者
    public void removeObserver(Observer o);
    
    //当主题发生数据变化时,通知全部观察
    public void notifyObservers();
    
}
复制代码

2.Observer

public interface Observer {

   /**
    * 
    * update:当气象站的观测数据发生改变时,这个方法会被调用
    * @param temp 温度
    * @param hunmidity 湿度
    * @param pressure  气压
    * @since JDK 1.6
    */
    public void update(float temp,float hunmidity,float pressure);
}
复制代码

3.DisplayElement

public interface DisplayElement {
    //当布告板须要展现时,调用此方法时
    public void display();
}
复制代码

4.新的WeatherData1

public class WeatherData1 implements Subject{
    
    private ArrayList<Observer> observers;
    
    private float temperature;
    
    private float humiditty;
    
    private float pressure;
    
    public WeatherData1(){
        observers=new ArrayList<Observer>();
    }

   //注册
    public void registerObserver(Observer o) {
        observers.add(o);
        
    }

    //删除
    public void removeObserver(Observer o) {
       int i=observers.indexOf(o);
       if(i>=0){
           observers.remove(i);
       }
        
    }

    //通知观察者数据变化
    public void notifyObservers() {
        for(int i=0;i<observers.size();i++){
            Observer observer=observers.get(i);
            observer.update(temperature, humiditty, pressure);
        }
        
    }
    
    public void measurementsChanged(){
        notifyObservers();
    }
    
    public void setMeasurements(float temperature,float humidity,float pressure){
        this.temperature=temperature;
        this.humiditty=humidity;
        this.pressure=pressure;
        measurementsChanged();
    }

}
复制代码

5.CurrentConditionsDisplay

public class CurrentConditionsDisplay implements Observer,DisplayElement{
    
    private float temperature;
    private float humidity;
    private float pressure;
    private Subject weatherData;
    
    public CurrentConditionsDisplay(Subject weatherData){
        this.weatherData=weatherData;
        weatherData.registerObserver(this);
    }
    
    /**
     * 
     * update:更新布告板内容
     * @author 吉祥
     * @param temperature
     * @param humidity
     * @param pressure
     * @since JDK 1.6
     */
    public void update(float temperature,float humidity,float pressure){
        this.temperature=temperature;
        this.humidity=humidity;
        this.pressure=pressure;
        display();
    }

    /**
     * 
     * display:展现布告板内容
     * @author 吉祥
     * @since JDK 1.6
     */
    public void display(){
        System.out.println("Current conditons:"+temperature
                +"F degrees and "+humidity+"% humidity");
    }
}
复制代码

6.ForecastDisplay

public class ForecastDisplay implements Observer,DisplayElement{
    private float temperature;
    private float humidity;
    private float pressure;
    private Subject weatherData;
    
    public ForecastDisplay(Subject weatherData){
        this.weatherData=weatherData;
        weatherData.registerObserver(this);
    }
    
    /**
     * 
     * update:更新布告板内容
     * @author 吉祥
     * @param temperature
     * @param humidity
     * @param pressure
     * @since JDK 1.6
     */
    public void update(float temperature,float humidity,float pressure){
        this.temperature=temperature;
        this.humidity=humidity;
        this.pressure=pressure;
        display();
    }

    /**
     * 
     * display:展现布告板内容
     * @author 吉祥
     * @since JDK 1.6
     */
    public void display(){
        System.out.println("Forecast: More of the same");
    }

}
复制代码

7.StatisticsDisplay public class StatisticsDisplay implements Observer,DisplayElement{ private float temperature; private float humidity; private float pressure; private Subject weatherData;

public StatisticsDisplay(SubjectweatherData){
    this.weatherData=weatherData;
    weatherData.registerObserver(this);
}

/**
 * 
 * update:更新布告板内容
 * @author 吉祥
 * @param temperature
 * @param humidity
 * @param pressure
 * @since JDK 1.6
 */
public void update(float temperature,float humidity,float pressure){
    this.temperature=temperature;
    this.humidity=humidity;
    this.pressure=pressure;
    display();
}

/**
 * 
 * display:展现布告板内容
 * @author 吉祥
 * @since JDK 1.6
 */
public void display(){
    System.out.println("Avg/Max/Min temperature= "+temperature
            +"/"+temperature+"/"+temperature);
}
复制代码

}

ps:这里在Observer中使用Subject缘由在于方便之后的取消注册。

最后咱们创建一个测试类WeatherStation来进行测试

public class WeatherStation {
    public static void main(String[] args){
        WeatherData1 weatherData=new WeatherData1();
        
        CurrentConditionsDisplay currentConditionsDisplay=new CurrentConditionsDisplay(weatherData);
        StatisticsDisplay statisticsDisplay=new StatisticsDisplay(weatherData);
        ForecastDisplay forecastDisplay=new ForecastDisplay(weatherData);
        
        weatherData.setMeasurements(80, 65, 30.4f);
        weatherData.setMeasurements(82, 70, 29.2f);
        weatherData.setMeasurements(78, 90, 29.2f);
    }
}
复制代码

最终结果以下

测试结果
到这里咱们已经讲解完观察者模式的一种实现方式。可是这咱们也提出一个问题,用来发散。

是否可以在主题中提供向外的可让观察者本身获取本身想要数据,而并不是将全部的数据都推送给观察者?也就是在Push(推)的同时咱们也能够pull(拉)。

4.Java内置的观察者模式

刚才的问题,其实熟悉Java语言的开发者会发现,在Java中已经有相应的模式,若是熟悉的能够直接跳过本章。 在java.util包下有Observer和Observable类,这两个类的结构跟咱们遇到的Subject和Observer模型有些相似。甚至但是随意使用push(推)或者pull(拉) 这里咱们使用在线的Java API网站在线Java API文档 首先查询Observer的API

Observer API文档

这个与咱们所写的Observer结构几乎相同,只是在推送是把Observable类一块儿推送,这样用户既能够push也可使用pull的方式。那么Observable的结构呢

Observable API 介绍

Observable API 方法介绍

咱们发现这里Observable是类与咱们以前Subject做为接口的方式稍微有区别;而且Observable类其余方法更全。那么使用类的方式和使用接口的影响咱们在后面会继续讲。而且这里咱们关注setChanged()方法告诉被观察者的数据发生改变 那么,若是要使用Java中自带的观察者模式来修改原有气象站业务会如何。

首先,咱们来分析更改后气象站的模型:

Java内置观察者模式 气象站

4.1Java内置观察者模式运做模式

相对于于以前Subject和Observer的模式,Java内置自带的观察者模式运行稍微有些差别。

  • 将对象变成观察者只须要实现Observer(java.util.Observer)接口,而后调用任何Observable的addObserver()方法便可。若是要删除观察者,调用deleteObserver()便可。

  • 被观察者若要推送通知,须要对象继承Observable(java.util.Observable)类,并先调用setChanged(),首先标记状态已经改变。而后调用notifyObservers()方法中的一个:notifyObservers()(通知观察者pull数据)或notifyObserers(Object object)(通知观察者push数据)

那么做为观察者如何处理被观察者推送出的数据呢。 这里逻辑以下:

  • 观察者(Observer)必须在update(Observable o,Object object).前一个参数用来让观察者知道是哪一个被观察者推送数据。后一个object为推送数据,容许为null。

4.2 setChanged()

在Observable类中setChanged()方法一开始我也有疑惑,为什么在推送以前须要调用该方法。后来查阅资料和Java API发现它很好的一个用处。咱们先来查看java的源码

Observable类中setChanged()方法
Observable类中notifyObservers()方法
这里必须标记为true才会推送消息,那么这个到底有何好处,咱们拿气象站模型来分析。 若是没有setChanged方法,也是以前的Subject和Observer模型里,一旦数据发生细微的变化,咱们都会对全部的观察者进行推送。若是咱们须要在温度变化1摄氏度以上才发送推送,调用setChanged()方法更加有效。固然,这个功能使用场景不多,可是也不排除会用到。固然更改Object和Observer模型也是能够作到这个效果的!!!

4.3 Java内置观察者更改气象站

那么利用气象站模型来实际操做一下,依照以前的模型咱们代码应该以下 1.WeatherData2

public class WeatherData2 extends Observable{
    
    private float temperature;
    
    private float humidity;
    
    private float pressure;
    
    //构造器不须要为了记住观察者创建数据模型
    public WeatherData2(){
        
    }
    
    
    public void measurementsChanged(){
        //在调用notifyObserver()须要指示状态已经更改了
        setChanged();
       //这里未使用notifyObserver(object),因此数据采用拉的逻辑
        notifyObservers(this);
    }
    
    public void setMeasurements(float temperature,float humidity,float pressure){
        this.temperature=temperature;
        this.humidity=humidity;
        this.pressure=pressure;
        measurementsChanged();
    }
    
    //如下方法为pull操做提供
    public float getTemperature() {
        return temperature;
    }
    
    public float getHumidity() {
        return humidity;
    }

    public float getPressure() {
        return pressure;
    }
}
复制代码

2.CurrentConditionsDisplay1

public class CurrentConditionsDisplay1 implements Observer,DisplayElement{
    
    private Observable observable;
    
    private float temperature;
    
    private float humidity;
    
    private float pressure;
    
    //构造器须要传入Observable参数,并登记成为观察者
    public CurrentConditionsDisplay1(Observable observable){
        this.observable=observable;
        observable.addObserver(this);
    }
    
    //update方法增长Observable和数据对象做为参数
    public void update(Observable o, Object arg) {
        if(arg instanceof WeatherData2){
            WeatherData2 weatherData2=(WeatherData2) arg;
            this.temperature=weatherData2.getTemperature();
            this.humidity=weatherData2.getHumidity();
            this.pressure=weatherData2.getPressure();
            display();
        }
        
    }
    
    /**
     * 
     * display:展现布告板内容
     * @author 吉祥
     * @since JDK 1.6
     */
    public void display(){
        System.out.println("Current conditons:"+temperature
                +"F degrees and "+humidity+"% humidity");
    }
}
复制代码

3.ForecastDisplay1

public class ForecastDisplay1 implements Observer,DisplayElement{
    private float temperature;
    private float humidity;
    private float pressure;
    private Observable observable;
    
    public ForecastDisplay1(Observable observable){
        this.observable=observable;
        observable.addObserver(this);
    }
    
   
    public void update(Observable o,Object arg){
        if(arg instanceof WeatherData2){
            WeatherData2 weatherData2=(WeatherData2) arg;
            this.temperature=weatherData2.getTemperature();
            this.humidity=weatherData2.getHumidity();
            this.pressure=weatherData2.getPressure();
            display();
        }
    }
        

    /**
     * 
     * display:展现布告板内容
     * @author 吉祥
     * @since JDK 1.6
     */
    public void display(){
        System.out.println("Forecast: More of the same");
    }

}
复制代码

4.StatisticsDisplay1

public class StatisticsDisplay1 implements Observer,DisplayElement{

    private float temperature;
    private float humidity;
    private float pressure;
    private Observable observable;
    
    public StatisticsDisplay1(Observable observable){
        this.observable=observable;
        observable.addObserver(this);
    }
    
   
    public void update(Observable o,Object arg){
        if(arg instanceof WeatherData2){
            WeatherData2 weatherData2=(WeatherData2) arg;
            this.temperature=weatherData2.getTemperature();
            this.humidity=weatherData2.getHumidity();
            this.pressure=weatherData2.getPressure();
            display();
        }
    }
        

    /**
     * 
     * display:展现布告板内容
     * @author 吉祥
     * @since JDK 1.6
     */
    public void display(){
        System.out.println("Avg/Max/Min temperature= "+temperature
                +"/"+temperature+"/"+temperature);
    }
}
复制代码

最后进行测试: WeatherStation1

public class WeatherStation1 {
    public static void main(String[] args){
        WeatherData2 weatherData=new WeatherData2();
        
        CurrentConditionsDisplay1 currentConditionsDisplay=new CurrentConditionsDisplay1(weatherData);
        StatisticsDisplay1 statisticsDisplay=new StatisticsDisplay1(weatherData);
        ForecastDisplay1 forecastDisplay=new ForecastDisplay1(weatherData);
        
        weatherData.setMeasurements(80, 65, 30.4f);
        weatherData.setMeasurements(82, 70, 29.2f);
        weatherData.setMeasurements(78, 90, 29.2f);
       
    }
}
复制代码

结果最终以下:

Java 内置观察者模式

咱们对比以前Subject和Observer的观察者模式会发现二者输出顺序不同,这是为何?

其实java.util.Observable不依赖于观察者被通知的顺序的,而且实现了他的notifyObserver()方法,这会致使通知观察者的顺序不一样于Subject和Observer模型在具体类实现notifyObserver()方法。其实二者都没有任何的代码偏差,只是实现的方式不一样致使不一样的结果。

可是java.util.Observable类却违背了以前第一章中针对接口编程,而非针对实现编程。恐怖的是,它也没有接口实现,这就致使它的使用具备很高的局限性和低复用性。若是一个对象不只仅是被观察者,同时仍是另外一个超类的子类的时候,咱们没法使用多继承的方式来实现。咱们若是自行拓展的话,你会发现setChanged()方法是protected方法,这就表示只有java.util.Observable自身和其子类才可使用这个方法。这就违反了第二个设计原则---------"多用组合,少用继承"。这也是我通常不会使用Java自带的设计者模式的缘由。

如今比较流行的观察者模式,也就是RxJava,可是因为这个框架涉及不只仅有观察这模式,在以后整个设计模式整理玩不后,我会集中再讲。

5.总结

到此,观察者模式的讲解已经所有讲解完成。总结一下。

第四设计原则 为交互对象之间的松耦合涉及而努力

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

相应的资料和代码托管地址github.com/jixiang5200…

相关文章
相关标签/搜索