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

一、介绍

  观察者(Observer)模式可以帮你的对象知悉现况,不会错过该对象感兴趣的事。

  对象甚至在运行时可决定是否要继续被通知。以及一对多关系,和松耦合。

  有了观察者,你将会消息灵通。

  观察者模式定义了对象之间的一对多依赖,这样一来当一个对象改变状态时,

  它的所有依赖着都会收到通知并自动更新。

  实现观察者模式的方法不止一种,但是以包含Subject和Observer接口的类设计的做法最常见。

 

二、引入

  恭喜贵公司获选为敝公司建立下一代Internet气象观测站!

  该气象站必须建立在我们专利申请中的WeatherData对象
  上,由WeatherData对象负责追踪目前的天气状况(温度、
  湿度、气压)。我们希望贵公司能建立一个应用,有三种
  布告板,分别显示目前的状况、气象统计及简单的预报。
  当WeatherObject对象获得最新的测量数据时,三种布告板
  必须实时更新。
  而且,这是一个可以扩展的气象站,Weather-O-Rama气象
  站希望公布一组API,好让其他开发人员可以写出自己的
  气象布告板,并插入此应用中。我们希望贵公司能提供这
  样的API。
  Weather-O-Rama气象站有很好的商业营运模式:一旦客
  户上钩,他们使用每个布告板都要付钱。最好的部分就是,
  为了感谢贵公司建立此系统,我们将以公司的认股权支付
  你。
  我们期待看到你的设计和应用的alpha版本。
  
  WeatherData对象知道如何跟物理气象站联系,以取得更新的数据。WeatherData对象随即更新三个布告板的显示:目前状况(温度、湿度、气压)、气象统计和天气预报。
  我们的工作就是建立一个应用,利用WeatherData对象取得数据,并更新三个布告板:目前状况、气象统计和天气预报。
  

   

  
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 

  报纸的订阅 出版者+订阅者=观察者模式
  出版者改称为“主题”(Subject),订阅者改称为“观察者”(Observe)。
  

 

 
 
 
设计原则:
  1、为了交互对象之间的松耦合设计而努力。
  松耦合的设计之所以能让我们建立有弹性的OO系统,能够应对变化,是因为对象之间的互相依赖降到了最低。
 
 三、实现
  

 

  Java为观察者模式提供了内置支持,但是许多时候,自己建立这一切会更具弹性。
  主题
1 public interface Subject {
2 
3     public void registerObserver(Observer o);
4     
5     public void removeObserver(Observer o);
6     
7     public void notifyObservers();
8 }

  主题实现类

 1 public class WeatherData implements Subject{
 2     private ArrayList<Observer> observers;
 3     private float temperature;
 4     private float humidity;
 5     private float pressure;
 6 
 7 //        我们加上一个ArrayList来纪录观察者,此ArrayList是在构造器中建立的。
 8     public WeatherData(){
 9         observers = new ArrayList<Observer>();
10     }
11     
12     @Override
13     public void registerObserver(Observer o) {
14         observers.add(o);
15     }
16 
17     @Override
18     public void removeObserver(Observer o) {
19         int i = observers.indexOf(o);
20         if (i >= 0){
21             observers.remove(i);
22         }
23     }
24 
25     @Override
26     public void notifyObservers() {
27         // 在这里,我们把状态告诉每一个观察者。因为观察者都实现了update(),所以我们知道如何通知它们。
28         for (int i = 0; i < observers.size(); i++) {
29             Observer observer = observers.get(i);
30             observer.update(temperature, humidity, pressure);
31         }
32     }
33 
34     public void measurementsChanged(){
35         // 当从气象站得到更新观测值 时,我们通知观察者。 
36         notifyObservers();
37     }
38     
39     public void setMeasurements(float temperature, float humidity, float pressure) {//模拟从气象站收到数据
40          this.temperature = temperature;
41          this.humidity = humidity;
42          this.pressure = pressure;
43          measurementsChanged();
44     }
45     
46 }

  观察者

1 public interface Observer {
2 
3 //    暗示:这些观测值的种类和个数在未来有可能改变吗?如果以后会改变,这些变化是否被很好地封装?或者是需要修改许多代码才能办到?
4     public void update(float temp, float humidity, float pressure);
5 }

  每个观察者必须实现的接口

1 public interface DisplayElement {
2 
3     public void display();
4 }

  观察者实现类

 1 public class CurrentConditionsDisplay implements DisplayElement, Observer {
 2     private float temperature;
 3     private float humidity;
 4     private Subject weatherData;
 5 
 6     // 构造器需要 weatherData对象(也就是主题)作为注册用。
 7     public CurrentConditionsDisplay(Subject weatherData) {
 8         this.weatherData = weatherData;
 9         weatherData.registerObserver(this);
10     }
11 
12     @Override
13     public void update(float temperature, float humidity, float pressure) {
14         this.temperature = temperature;
15         this.humidity = humidity;
16         display();                    // 这里并不是调用display()最合适的地方。
17     }
18 
19     @Override
20     public void display() {
21         System.out.println("Current conditions: "+ temperature +
22             "F degrees and " + humidity +"%humidity");
23     }
24 
25 }

  测试

 1 public class WeatherStation {
 2 
 3     public static void main(String[] args) {
 4         // 主题
 5         WeatherData weatherData = new WeatherData();
 6         // 订阅者 向 主题 注册
 7         CurrentConditionsDisplay currentDisplay = new CurrentConditionsDisplay(weatherData);    // Subject引用可能在取消订阅时用到。
 8         
 9         // 模拟从气象站取得数据
10         weatherData.setMeasurements(80, 65, 30.4f);   // Current conditions: 80.0F degrees and 65.0%humidity
11     }
12 }
 
 
 四、变化
  JohnnyAHurricane(Weather-O-Rama气象站的CEO)刚刚来电告知,他们还需要酷热指数(HeatIndex)布告板,这是不可或缺的。
  细节如下:
  酷热指数是一个结合温度和湿度的指数,用来显示人的温度感受。可以利用温度T和相对湿度RH套用下面的公式来计算酷热指数:
   一个关于温度和相对湿度的计算公式。
 
  新注册一个观察者HeadIndexDisplay
 1 public class HeatIndexDisplay implements Observer, DisplayElement {
 2     private float tempreture;
 3     private float humidity;
 4     private Subject weatherData;
 5     
 6     public HeatIndexDisplay(Subject weatherData) {
 7         this.weatherData = weatherData;
 8         weatherData.registerObserver(this);
 9     }
10 
11     @Override
12     public void display() {
13         float T = tempreture;
14         float RH = humidity;
15                 // 公式不对
16         float heatIndex =
17                 (float) (16.923 + 1.85212 * 10-1 * T + 5.37941 * RH - 1.00254 * 10-1 * T 
18         * RH + 9.41695 * 10-3 * T*T + 7.28898 * 10-3 * RH*RH + 3.45372 * 10-4
19         * T*T * RH - 8.14971 * 10-4 * T * RH*RH + 1.02102 * 10-5 * T*T * RH*RH - 
20         3.8646 * 10-5 * T*T*T + 2.91583 * 10-5 * RH*RH*RH + 1.42721 * 10-6 * T*T*T * RH 
21         + 1.97483 * 10-7 * T * RH*RH*RH - 2.18429 * 10-8 * T*T*T * RH*RH + 8.43296 * 
22         10-10 * T*T * RH*RH*RH - 4.81975 * 10-11 * T*T*T * RH*RH*RH);
23         
24         System.out.println("HeadIndex is" + heatIndex);
25     }
26 
27     @Override
28     public void update(float tempreture, float humidity, float pressure) {
29         // TODO Auto-generated method stub
30         this.tempreture = tempreture;
31         this.humidity = humidity;
32         display();
33 
34     }
35 
36 }

  

五、讨论

   主题和观察者就 使观察者获得状态信息的正确方式:
  1、主题主动推送数据
     方便不需要再去拉数据,但是有时候不需要的也会推送过来。
  2、观察者主动取数据
     用时去取,主题需要封装、再开放setter避免暴露过多信息。
   java内置Observer这两种都支持。
   java.util包(package)内包含最基本的Observer接口与Observable类,这和Subject接口与Observer接口很相似。
  Observer接口与Observable类使用上更方便,因为许多功能都已经事先准备好了。
  甚至可以使用推(push)或拉(pull)的方式传送数据。
   

   setChanged()方法的作用:

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

  比方说,如果没有setChanged()方法,我们的气象站测量是如此敏锐,以致于温度计读数每十分之一度就会更新,

  这会造成WeatherData对象持续不断地通知观察者,我们并不希望看到这样的事情发生。

  如果我们希望半度以上才更新,就可以在温度差距到达半度时,调用setChanged(),进行有效的更新。

   主题
 1 public class WeatherData extends Observable{
 2     
 3     private float temperature;
 4     private float humidity;
 5     private float pressure;
 6     
 7 //    "拉"察者会利用这些方法取得WeatherData对象的状态
 8     public float getTemperature() {
 9         return temperature;
10     }
11 
12     public float getHumidity() {
13         return humidity;
14     }
15 
16     public float getPressure() {
17         return pressure;
18     }
19 
20     public void setMeasurements(float temperature, float humidity, float pressure) {
21          this.temperature = temperature;
22          this.humidity = humidity;
23          this.pressure = pressure;
24          measurementsChanged();
25     }
26     
27     public void measurementsChanged(){
28         setChanged();
29         notifyObservers();  //注意:我们没有调用notifyObservers()传送数据对象,这表示我们采用的做法是“拉”。
30     }
31 }

  观察者

 1 import java.util.Observable;
 2 import java.util.Observer;
 3 
 4 public class CurrentConditionsDisplay implements Observer,DisplayElement {
 5     
 6     private float temperature;
 7     private float humidity;
 8     Observable o;
 9     
10     public CurrentConditionsDisplay(Observable o) {
11         this.o = o;
12         o.addObserver(this);
13     }
14     
15     @Override
16     public void update(Observable obs, Object arg) {
17         if(obs instanceof WeatherData){
18             WeatherData weatherData = (WeatherData) obs;
19             this.temperature = weatherData.getTemperature();
20             this.humidity = weatherData.getHumidity();
21             display();
22         }
23     }
24     @Override
25     public void display() {
26         System.out.println("Current conditions: "+ temperature +
27             "F degrees and " + humidity +"%humidity");
28     }
29 
30 }

 

   内置观察者模式的问题:

     1、通知观察者的次序不一定是其注册的次序。
     可以确定的是,如果我们的代码依赖之前的次序,就是错的。
    一旦观察者/可观察者的实现有所改变,通知次序就会改变,很可能产生错误的结果,也不是我们所认为的松耦合。
    2、java.util.Observable是一个类而不是一个接口
    如果某类想同时具有Observable类和另一个超类的行为,就会陷入两难,毕竟Java不支持多重继承。
    这限制了Observable的复用潜力(而增加复用潜力不正是我们使用模式最原始的动机吗?)。
    再者,因为没有Observable接口,所以你无法建立自己的实现,和Java内置的Observer API搭配使用。
    3、Observable将关键的方法保护起来
    如果你看看Observable API,你会发现setChanged()方法被保护起来了(被定义成protected)。
    这意味着:除非你继承自Observable,否则你无法创建Observable实例并组合到你自己的对象中来。
    这个设计违反了第二个设计原则:“多用组合,少用继承”。
 
六、总结
  
  观察者模式定义了对象之间一对多的关系。
 
  主题(也就是可观察者)用一个共同的接口来更新观察者。
 
  观察者和可观察者之间用松耦合方式结合(loosecoupling),可观察者不知道观察者的细节,只知道观察者实现了观察者接口。
 
  使用此模式时,你可从被观察者处推(push)或拉(pull)数据(然而,推得方式被认为更“正确”)。
 
  有多个观察者时,不可以依赖特定的通知顺序。
 
  Java有多种观察者模式的实现,包括了通用的java.util.Observable。
 
  要注意java.util.Observable所带来的的一些问题。
 
  如果有必要的话,可以实现自己的Observable。
  
  设计原则:
    1、封装变化
    2、多用组合,少用继承
    3、针对接口编程,不针对实现编程
    4、为交互之间的松耦合设计儿努力       // 新原则
 
   『观察者模式』Obverse Pattern 在对象之间定义一对多的依赖,这样一来,当一个对象改变状态,依赖它的其他对象都会收到通知,并自动更新。
 
   一个新的模式,以松耦合方式在一系列对象之间沟通状态。我们目前还没看到观察者模式的代表人物——MVC。
   
 
 
 
 
 
 
 
posted @ 2020-05-18 16:59  归零19  阅读(266)  评论(0编辑  收藏  举报