美文网首页
设计模式之——观察者模式

设计模式之——观察者模式

作者: FeidianJava | 来源:发表于2018-11-25 01:21 被阅读89次

    前提:本文章是以Java为基础写的

    一、定义描述

    什么是观察者模式?
    既然我们要探讨一下观察者模式,首先还是先说一下他的定义吧;

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

    现在看不懂定义很正常,在我们接下来的探讨中你就会慢慢的了解了,在我们的探讨结束之后,再回头看这个定义,你就会有所领悟了!

    接下来我们就步入正题了!

    二、实例探讨

    例子:
    现在要求我们做一个气象监测应用,这个系统大致分为三个部分,分别是气象站(获取实际气象数据的装置)、WeatherData对象(追踪来自气象站的数据,并更新布告板)和布告板(显示目前天气状况给用户看)。
    WeatherData对象知道如何跟物理气象站联系,以取得更新的数据(这个我们不需要考虑)。WeatherData对象会随即更新三个布告板的显示:目前状况(温度、湿度、气压)、气象统计和天气预报。

    我们的工作:

    利用WeatherData对象取得数据,并更新三个布告板:目前状况、气象统计和天气预报。

    已知的一些内容如下:

    WeatherData类有四个方法:getTemperature();
    getHumidity();
    getPressure() ;前三个方法各自返回最近的气象测量数据(温度、湿度、气压)
    measurementsChanged();此方法在气象测量更新时调用
    其实我们的工作就是实现measurementsChanged(),让他更新目前状况、气象统计、天气预报的显示布告板

    我们现在需要解决的的问题

    ①实现三个使用天气数据的布告板:“目前状况”布告、“气象统计”布告、“天气预告”布告。只要WeatherData有新的测量,这些布告就必须马上更新。
    ②系统必须可扩展,这是什么意思呢?
    就是其他开发人员可以建立定制的布告板,用户可以随心所欲地添加删除任何布告板


    OK,这个题我们就分析到这里,下面就要上代码了!
    代码:

    public class WeatherData{
         //实例变量声明
         public void measurementChanged(){
               float temp = getTemperature();
               float humidity = getHumidity();
               float pressure = getPressure();
    
               currentConditionsDisplay.update(temp,humidity,pressure);//目前状况布告板的更新
               statisticsDisplay.update(temp,humidity,pressure);//气象统计布告板的更新
               forecastDisplay.update (temp,humidity,pressure);//天气预报布告板的更新
         }
     //这里是其他WaetherData方法  
    }
    

    What?

    难道我们这样就解决了?so easy?好嗨哟?!
    很不幸的告诉你,这个是

    错误示范

    接下来我们先探讨一下上面的错误示范:

    先上道题瞅瞅


    答案是ABCE
    错误代码分析:

    ——现在回头看一下刚才的代码,有没有发现三个布告板都有一个update(),并且里面的形参也是一样的,那我们是不是可以把它搞成一个统一的接口呢?
    ——前面我们提到过,我们可能还要增加或删除布告板,那这样的话,我们岂不是要一直修改程序?


    接下来我们就先来认识一下“观察者模式”这位朋友

    三、认识观察者模式

    我们还是通过一个例子来了解一下观察者模式吧
    例子:
    如果你了解报纸的订阅是怎么回事,其实就知道观察者模式是怎么回事,只是名称不太一样:出版者对应“主题”(Subject),订阅者对应“观察者”(Observer)。
    其实总的来说:

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

    为了让大家看的更明白点,还是奉上图吧:



    下面是鸭子、老鼠和主题的故事:(加深一下大家对观察者模式的理解)


    四、定义观察者模式

    观察者模式的定义我们在最开始已经说过了,经过上面我们对他的了解,现在我们再来回顾一下:

    ——主题和观察者定义了一对多的关系。
    ——观察者依赖于此主题,只要主题状态一有变化,观察者就会被通知。根据通知的风格,观察者可能因此新值而更新。
    下面大家先看一下观察者模式的类图:


    看了这张图你可能还会有些疑惑,不用慌,下面请欣赏我的自问自答:

    问:

    这和一对多的关系有啥关联啊?

    答:

    利用观察者模式,主题是具有状态的对象,并且可以控制这些状态。意思就是,有“一个”具有状态的主题。另外,观察者使用这些状态,虽然这些状态并不属于他们。观察者是有很多的,他们依赖主题来告诉他们状态何时改变。这就产生一个关系:

    “一个”主题对“多个”观察者的关系。

    问:

    其中的依赖是咋产生的啊?

    答:

    因为主题是真正拥有数据的人,观察者是主题的依赖者,在数据变化时更新,这样比起让许多对象控制同一份数据来,可以得到更干净的00设计
    不知道00设计的可以点击下面的连接:
    https://www.cnblogs.com/HigginCui/p/6195318.html


    说了这么多了咱们上面那个实例的问题还没有解决,不过相信大家现在对观察者模式了解的也差不多了,所以马上就到解决问题的时候了
    现在呢,我们再来加深一下对观察者模式的了解:

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

    (这里的松耦合的意思就是:主题和观察者联系越小越好


    五、解决实例的问题

    首先我们还是要先捋一下我们的思路:

    观察者模式是定义了一对多的依赖
    ——WeatherData类就是“一”,而“多"就是使用天气观测的各种布告板。

    观察者模式中主题是具有状态的对象
    ——WeatherData对象正好是有状态的(温度、湿度、气压)。

    观察者模式是主题+观察者
    ——我们可以把WeatherData对象当作主题,把布告板当作观察者。

    ——布告板获取信息,需要向WeatherData注册。

    ——每个布告板都有差异,我们就用一个共同的接口。
    好了,差不多了,设计图就可以画出来了:


    有了设计图,接下来就要干正事了!代码:
    public interface Subject{
          public void registerObserver(Observer o);//注册观察者
          public void removeObserver(Observer o);//删除观察者
          public void notifyObserver();//主题改变时,这个方法会被调用,通知所有的观察者
    }
    
    public interface Observer{
          public void update(float temp, float humidity, float pressure);//观察者用于更新数据
    }
    
    public interface DisplayElement{
          public   void display();//布告板需要显示时,调用此方法
    }
    
    小探讨:

    你可能会有其他想法用来实现这个问题,可能你会觉得你的方法比这个简单,代码很短,但是在你需要对这个气象应用进行扩展的时候,比如添加新的布告板,添加新的功能,你用接口的话就不需要修改很多的代码,直接写你要添加的布告板就可以了,不需要做过多的修改,有很好的封装性。
    你可以自己写下试试,比较一下。


    接口写好了,下面就实现我们的气象站
    代码:

    public class WeatherData implements Subject{
          private ArrayList observer;//我们用ArrayList数组来记录观察者
          private float temperature;
          private float humidity;
          private float pressure;
    
          public WeatherData(){
                observers=new ArrayList();
          }//在构造器中建立ArrayList
    
          public void registerObserver(Observer o){
                observers.add(o);
          }//注册观察者,直接加到ArrayList的后面就行了
          
          public void removeObserver(Observer o){
                int i = observers.indexOf(o);            if(i >= 0){
                      observers.remove(i);
                }
          }//跟注册类似,取消观察者,直接把他从ArrayList中删除
    
          public void notifyObservers(){
                for(int i = 0; i < observers.size(); i++){
                      Observer observer=(Observer)observers.get(i);
                      Observer.update(temprature, humidity, pressure);
                }
          }//我们把状态告诉每一个观察者,通过updata()通知他们
    
          public void measurementsChanged(){
                notifyObservers();
          }//当从气象站得到更新观测值时,我们通知观察者
    
          public void setMeasurements(float temperature, float humidity, float pressure){
                this.temperature = temprature;
                this.humidity = humidity;
                this.pressure = pressure;
                measurementsChanged();
          }
    //WeatherData的其他方法
    }
    
    ArrayList的相关:

    https://www.cnblogs.com/rickie/articles/67978.html


    上面的WeatherData类已经写好了,下面就该布告板了,三个布告板:目前状况布告板、气象统计布告板和天气预报布告板(由于三个布告板基本差不多,所以这里就只写目前状况布告板)
    代码:

    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);
          }//构造器,用weatherData对象作为注册来用
    
          public void update(float temperature, float humidity, flaot pressure){
                this.temperature = temperature;
                this.humidity = humidity;
                display();
          }
    
          public void display(){
                System.out.println("Current conditions: " + temperature + "F degress and " + humidity + "% humidity");
          }//把最近的温度和湿度显示出来
    }
    

    以上代码并不是最好的,大家可以思考一下,尝试不同的方法!


    接下来就剩把这些连接起来的测试程序了,Let's go!
    代码:
    (这里也只写了目前状况布告板)

    public class WeatherStation{
          public static void main(String[] args){
                WeatherData weatherData = new WeatherData();//建立WeatherData对象
                CurrentConditionsDisplay currentDisplay = new CurrentConditionsDisplay(weatherData);//建立布告板,传入WeatherData对象
                weatherData.setMeasurements(80,65,30.4f);
                weatherData.setMeasurements(82,70,29.2f);
                weatherData.setMeasurements(78,90,29.2f);这三组数据是我瞎写的
        }
    }
    

    请自行运行程序测试吧!
    终于欧克了啊,观察者模式不过如此嘛!哈哈哈!


    本来到这里是应该大结局的,可是Java实力太强,他不允许啊!

    六、扩展内容

    Java内置的观察者模式:

    ——Java API 内置的
    ——java.util包内有Observer接口与Observable类,这跟我们的Subject接口与Observer接口是类似
    ——使用贼方便,许多功Java API都给你准备好了
    下面我们先了解一下java.util.Observer和java.util.Observable,下面的图是修改后的气象站00设计:


    Java内置的观察者模式的运作

    其实跟我们在气象站中的实现差不多,就是WeatherData(我们的主题)不太一样,现在这个是扩展自Observable类,诸如增加、删除、通知观察者的方法都继承到了,直接调用就行了。
    用法:
    ——先介绍一下我们会用到的内置方法:
    setChanged()方法:标记状态已经改变的事实;
    notifyObservers()方法:数据更新时通知观察者(或者nitifyObservers(Object arg)方法;
    update(Observable o,Object arg)方法:这个就是观察者更新数据的方法;

    在实现代码之前,先说一下这个setChanged()方法,因为之前咱没有标记状态改变这个步骤,所以这里讲一下用它的原因。
    看一下下面的代码你就懂了:

    setChanged(){
          changed = true;
    }
    
    notifyObservers(Object arg){
          if(changed){
                for ever observer on the list{
                      call update(this,arg)
                }
                changed = false;//通知观察者之后,把changed标为false
          }
    }//这里只会在setChanged()中的changed标为“true”时才会通知观察者
    
    notifyObservers(){
          notifyObservers(null);
    }
    

    你可能会问:

    这样做有神马必要呢?

    我来告诉你:
    我们用setChanged()方法可以在更新观察者是,有更多的弹性,可以更适当地通知观察者。还是打个比方发吧,如果我们不用setChanged()方法,我们的气象站测量还非常灵敏,以致于温度计读数每 1/10 就会更新,这样就会造成WeatherData对象不断地通知观察者,你想这样吗?是不是太烦人了?如果我们用setChanged()方法,我们就可以自行设置,比如我们想要在半度以上才更新,我们就可以在温度度差到达半度时,调用setChanged()方法,进行有效的更新(现在主动权就掌握在我们手中了,说让他啥时候通知就啥时候通知,真好!)


    ok,接下来我们就利用Java内置的支持

    重做气象站:
    import java.util.Observable;
    import java.util.Observer;
    
    public class WeatherData extends Observable{   //看好啊,我们现在正在**继承**Observable
          private float temperature;
          private float humidity;
          private float pressure;
    
          public WeatherData(){ }//构造器不需要为了记住观察者而建立ArrayList了
    
          public void measurementsChanged(){
                setChanged();//这里,在调用notifyObservers()之前,先调用setChanged()来指示状态已经改变
                notifyObservers();
          }
    
          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;
          }
    //用上面这三个方法取得WeatherData对象的状态
    }
    
    重做目前状况布告板:
    import java.util.Observable;
    import java.util.Observer;
    
    public class CurrentConditionsDisplay implements Observer, DisplayElement{  //我们这里正在实现java.util.Observer接口
          Observable observable;
          private float temperature;
          private float humidity;
    
          public CurrentConditionsDisplay(Observable observable){ //构造器需要Observable当参数,并将CurrentConditionsDisplay对象登记成为观察者
                this.observable = observable;
                observable.addObserver(this);
          }
          
          public void update(Observable obs, Object arg){  //改变update()方法,增加Observable和数据对象作为参数
                if(obs instanceof WeatherData){   //这里的instanceof的作用是:确定可观察者(主题)属于WeatherData类型
                      WeatherData weatherData = (WeatherData)obs;
                      this.temperature = weatherData.getTemperature();
                      this.humidity = weatherData.getHumidity();
                      display();
                }
          }
    
          public void display(){
                System.out.println("Current conditions: " + temperature + "F degress and " + humidity + "% humidity");
          }
    }
    
    这里是对instanceof的介绍:

    https://www.cnblogs.com/zjxynq/p/5882756.html
    来看一下运行结果吧:


    这是完整版的运行结果,你们记得把另外两个布告板也写上去!

    通过看这个运行结果,我们能看出啥呢?

    emmm...他这个输出次序好像有点乱啊,本来的次序是:目前状况—>气象统计—>天气预报;

    那现在咋成这样了呢?思考一下,我们继续

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

    java.util.Observable实现了他的notifyObservers()方法,这导致了通知者的次序不同于我们先前的次序。谁都没错,只是双方选择不同的方式罢了。

    但是,我们的依赖这样的次序来写,就是错的!这样一来,只要观察者的实现有所变化,通知次序就会改变,很有可能就会产生错误的结果。所以这就不可能是我们所要的松耦合(违背了初心啊!)

    下面就来看一下原因吧!


    现在应该明白了吧?
    我在前面的代码的注释中就特别说明了Observable是,而非是我们想要的接口

    除了这个内置的观察者模式,下面还有一些扩展,继续看下去吧
    Come on!!!

    观察者与Swing

    其实观察者模式还是比较厉害的,不仅是在java.util中可以见到,而且在JavaBeansSwing中,也都有观察者模式。
    ——JavaBeans中的观察者模式的话,可以直接查一下PropertyChangeListener接口。
    ——Swing中的,我们就用例子来说一下:

    一个小的、改变生活的程序

    程序非常简单,有一个按钮,上面写着“Should I do it ?”(我该做吗?)。当你按下按钮,倾听者(观察者)就回答问题。我们这里就实现两个倾听者吧,一个天使(AngelListener),一个恶魔(DevilListener)。
    提示:我们这里用的是Swing API:JButton
    这个程序代码很短,就建立一个JButton对象,把他加到JFrame,然后设置好倾听者(观察者)就可以了。我们这里打算用内部类作为倾听者。
    代码:

    public class SwingObserverExample{
          JFrame frame;
          
          public static void main(String[] args){
                SwingObserverExample example = new SwingObserverExample();
                example.go();
          }
          public void go(){
                frame = new JFrame();
                JButton button = new JButton("Should I do it ?");
                button.addActionListener(new AngelListener());
                button.addActionListener(new DevilListener());
                frame.getContentPane().add(BorderLayout.CENTER, button);//在这里设置frame的属性
          }
    
          class AngelListener implements ActionListener{
                public void actionPerformed(ActionEvent event){
                      System.out.println("Don't do it, you might regret it !");
                }
          }//天使倾听者(观察者)
    
          class DevilListener implements ActionListener{
                public void actionPreformed("Come on, do it !");
                }//这里的actionPerformed()对应之前的update()
          }//恶魔倾听者(观察者)
    }
    

    最后一个板块:

    七、总结

    工具

    ——00原则:

    • 封装变化;
    • 多用组合,少用继承;
    • 针对接口编程,不针对实现编程;
    • 为交互对象之间的松耦合设计而努力。
    要点
    • 观察者模式定义了对象之间一对多的关系;

    • 可观察者(主题)用一个共同的接口来更新观察者;

    • 观察者和可观察者(主题)之间用松耦合方式结合,可观察者不知道观察者的细节,只知道观察者实现了观察者接口;

    • 有多个观察者时,不可以依赖特定的通知次序;

    • Java有多种观察者模式的实现,包括了通用的java.util.Observable;

    • 可以尝试实现自己的Observable;

    • Swing大量使用观察者模式,许多GUI框架也是如此;

    • 此模式也被应用在很多地方,例如:JavaBeansRMI
      这次的观察者模式算是真正的讲完了,写的不咋样,大家凑合着看吧,有不懂的可以评论或者私聊!
      关于代码中的命名问题,这里推荐大家看一下《阿里Java开发手册》
      (提取码:aviz)

      *本文图片例子来源于《Head First 设计模式》*
      

    相关文章

      网友评论

          本文标题:设计模式之——观察者模式

          本文链接:https://www.haomeiwen.com/subject/aeghqqtx.html