NullPointer 发表于 2016-11-28 15:43

Java观察者模式

一、观察者模式定义:定义了对象之间的一对多依赖,这样一来,当一个对象改变状态时,他的所有依赖者都会受到通知并自动更新。观察者提供了一种对象设计,让主题和观察者之间松耦合。二、组成部分:1、抽象目标角色(Subject):也称主题,目标角色知道他的观察者,可以有任意多个观察者观察同一个目标,并且提供注册和删除观察者对象的接口。目标角色往往由抽象类或者接口来实现。2、抽象观察者角色(Observer):也称观察者,为那些在目标发生改变时需要获得通知的对象定义一个更新接口。抽象观察者角色主要由抽象类或接口来实现。3、具体目标角色(Concrete Subject):也称主题的实现,将有关状态存入各个Concrete Observer对象。当他的状态发生改变时,向他的各个观察者发出通知。4、具体观察者角色(Concrete Observer):也称观察者的实现,存储有关状态,这些状态应与目标的状态保持一致。实现Observer的更新接口以使自身状态保持一致。在本角色内也可以维护一个指向Concrete Subject对象的引用。三、设计原则(只是一部分设计原则,所有设计模式都通用):1、为了交互对象之间的松耦合设计而努力。松耦合的设计之所以能让我们建立有弹性的OO系统,能够应对变化,是因为对象之间的互相依赖降到了最低。2、找出程序中会变化的方面,然后将其和固定不变的方面相分离(在观察者模式中,会改变的是主题的状态,以及观察者的数目和类型。用这个模式,你可以改变依赖于主题状态的对象,却不必改变主题,则就叫提前规划!)3、针对接口编程,不针对实现编程。(主题与观察者都使用接口,观察者利用主题的接口向主题注册,而主题利用观察者接口通知观察者,这样可以让两者之间运作正常,又同时具有松耦合的优点)4、多用组合,少用继承(观察者模式利用“组合”将许多观察者组合进主题中。对象之间的这种关系不是通过继承产生的,而是在运行时利用组合的方式而产生的)四、应用场景:1、当一个对象的改变需要给其他对象时,而且他不知道具体有多少个对象有待改变时,可以采用观察者模式。2、一个抽象模型有两个方面,当其中一个方面依赖于另一个方面,这时用观察者模式可以将这两者封装在独立的对象中使他们各自独立的改变和复用。五、优缺点:优点:观察者模式解除了主题和具体观察者的耦合,让耦合的双方都依赖于抽象,而不是依赖具体,从而使得各自的变化都不会影响另一边的变化。缺点:依赖关系并未完全解除,抽象通知者依旧依赖抽象的观察者。六、具体的Demo
需求:需要三个板块来显示出天气状况,分别为:目前状况板块、气象统计板块、天气预报板块。目前状况板块需要显示“温度、湿度、气压”。这些数据需要从一个气象站来获取,并且需要及时获取,只要气象站更新数据,这三个板块上就要同步的显示出来。方案1:我们自己实现观察者模式,不借用任何API:1、首先定义一个主题(Subject)的接口,里面毋庸置疑需要有注册观察者、移除观察者的方法,此外还需要一个推送消息给观察者的方法。2、还需要一个观察者(Observer)的接口,里面只有一个update的更新数据的方法3、最后一个就是板块(DisplayElement)接口了,里面只有一个方法,display,负责展示数据的方法4、接下来就是需要写一个实现类负责实现主题接口,和三个板块类负责实现观察者接口以及板块接口。且三个板块的实现类还需要有一个主题的指针来指向主题实现类的对象。 view plain copy
https://code.csdn.net/assets/CODE_ico.pnghttps://code.csdn.net/assets/ico_fork.svg


[*]package observer;
[*]
[*]/**
[*] *
[*] * 描述:主题接口
[*] * @author chentongwei
[*] * @date 2016年5月24日下午12:46:52
[*] * @version 1.0
[*] */
[*]public interface Subject {
[*]    //注册观察者方法
[*]    void registerObserver(Observer o);
[*]    //注销观察者方法
[*]    void removeObserver(Observer o);
[*]    //负责向观察者推送数据的方法
[*]    void notifyObservers();
[*]}

view plain copy
https://code.csdn.net/assets/CODE_ico.pnghttps://code.csdn.net/assets/ico_fork.svg


[*]package observer;
[*]
[*]import java.util.ArrayList;
[*]
[*]/**
[*] *
[*] * 描述:主题的实现类
[*] * @author chentongwei
[*] * @date 2016年5月24日下午12:51:18
[*] * @version 1.0
[*] */
[*]public class WeatherData implements Subject {
[*]    //观察者的集合
[*]    private ArrayList<Observer> observers;
[*]    //温度
[*]    private float temperature;
[*]    //湿度
[*]    private float humidity;
[*]    //气压
[*]    private float pressure;
[*]      
[*]    public WeatherData() {
[*]      observers = new ArrayList<Observer>();
[*]    }
[*]      
[*]    //注册观察者
[*]    @Override
[*]    public void registerObserver(Observer o) {
[*]      if(null != o) {
[*]            observers.add(o);
[*]      }
[*]    }
[*]      
[*]    //移除观察者
[*]    @Override
[*]    public void removeObserver(Observer o) {
[*]      if(null != o && observers.indexOf(o) >= 0) {
[*]            observers.remove(observers.indexOf(o));
[*]      }
[*]    }
[*]      
[*]    //负责推送消息给观察者
[*]    @Override
[*]    public void notifyObservers() {
[*]      for(int i = 0; i < observers.size(); i ++) {
[*]            Observer observer = observers.get(i);
[*]            observer.update(temperature, humidity, pressure);
[*]      }
[*]    }
[*]      
[*]    //当从气象站的到更新观测值时,我们通知观察者
[*]    public void measurementsChanged() {
[*]      notifyObservers();
[*]    }
[*]      
[*]    public void setMeasurements(float temperature, float humidity, float pressure) {
[*]      this.temperature = temperature;
[*]      this.humidity = humidity;
[*]      this.pressure = pressure;
[*]      measurementsChanged();
[*]    }
[*]}

view plain copy
https://code.csdn.net/assets/CODE_ico.pnghttps://code.csdn.net/assets/ico_fork.svg


[*]package observer;
[*]
[*]/**
[*] *
[*] * 描述:观察者接口,所有的气象组件都实现此观察者接口
[*] * @author chentongwei
[*] * @date 2016年5月24日下午12:48:55
[*] * @version 1.0
[*] */
[*]public interface Observer {
[*]      
[*]    //负责更新数据
[*]    void update(float temp, float humidity, float pressure);
[*]}

view plain copy
https://code.csdn.net/assets/CODE_ico.pnghttps://code.csdn.net/assets/ico_fork.svg


[*]package observer;
[*]
[*]/**
[*] *
[*] * 描述:板块的共同接口
[*] * @author chentongwei
[*] * @date 2016年5月24日下午12:49:39
[*] * @version 1.0
[*] */
[*]public interface DisplayElement {
[*]    //负责显示数据的方法
[*]    void display();
[*]}

view plain copy
https://code.csdn.net/assets/CODE_ico.pnghttps://code.csdn.net/assets/ico_fork.svg


[*]package observer;
[*]
[*]/**
[*] *
[*] * 描述:观察者的实现类
[*] * @author chentongwei
[*] * @date 2016年5月24日下午1:06:16
[*] * @version 1.0
[*] */
[*]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 degress and " + humidity + "% humidity");
[*]    }
[*]
[*]    //负责更新数据
[*]    @Override
[*]    public void update(float temp, float humidity, float pressure) {
[*]      this.temperature = temp;
[*]      this.humidity = humidity;
[*]      display();
[*]    }
[*]      
[*]    public static void main(String[] args) {
[*]      WeatherData weatherData = new WeatherData();
[*]      CurrentConditionsDisplay conditionsDisplay = new CurrentConditionsDisplay(weatherData);
[*]      weatherData.setMeasurements(80, 65, 30.4f);
[*]      weatherData.setMeasurements(82, 70, 29.2f);
[*]      weatherData.setMeasurements(78, 90, 30.4f);
[*]    }
[*]}


方案2:我们利用Java内置的观察者模式:JavaAPI内置有观察者模式,java.util包(package)内包含最基本的Observer接口和Observable类,这和我们的自定义的Subject接口与Observer接口很相似。1、我们不需要定义一个主题接口,我们只需要声明一个类去继承java.util包下的Observable类。2、我们不需要定义一个观察者接口,我们只需要声明一个类去实现java.util下的Observer接口3、板块接口同方案1方案2的解答疑问:(一)如何把对象变成观察者?和以前一样,实现观察者接口(java.util.Observer),然后调用任何Observable对象的addObserver()方法。不想再当观察者时,直接调用deleteObserver()方法就可以了。(二)可观察者(主题)要如何送出通知?首先,你需要利用扩展java.util.Observable接口产生“可观察者”类,然后,需要如下两个步骤①先调用setChange()方法,标记状态已经改变的事实。说明:setChange()方法用来标记状态已经改变的事实,好让notifyObservers()知道当他被调用时应该更新观察者,如果调用notifyObservers()之前没有先调用setChange(),观察者就不会被通知。如下是Observable的内部源码:
view plain copy
https://code.csdn.net/assets/CODE_ico.pnghttps://code.csdn.net/assets/ico_fork.svg


[*]setChange() {
[*]    changed = true;
[*]}
[*]notifyObservers(Object arg) {
[*]    if(changed) {
[*]      for every observer on the list {
[*]            call update(this, arg);
[*]      }
[*]      changed = false;
[*]    }
[*]}
[*]notifyObservers() {
[*]    notifyObservers(null);
[*]}


②然后调用两种notifyObservers()方法中的其中一个:notifyObservers()或notifyObservers(Object arg)//当通知时,此版本可以传送任何的数据对象给每一个观察者(三)观察者如何接收通知?同以前方法一样,观察者实现了更新的方法,但是方法的签名不太一样:/*** o:主题本身当做第一个变量,好让观察者知道是哪个主题通知他的* arg:为上面notifyObservers(Objectarg)中的arg* arg若为null,则说明没有数据* * 如果你想“推”数据给观察者,你可以将数当做数据对象传送给notifyObservers(arg)方法,否则* 观察者就必须从可观察者对象中“拉”数据*/update(Observable o, Object arg)具体Demo如下:
view plain copy
https://code.csdn.net/assets/CODE_ico.pnghttps://code.csdn.net/assets/ico_fork.svg


[*]package observer;
[*]
[*]import java.util.Observable;
[*]
[*]/**
[*] *
[*] * 描述:主题的实现类
[*] * @author chentongwei
[*] * @date 2016年5月24日下午4:06:46
[*] * @version 1.0
[*] */
[*]public class WeatherData2 extends Observable {
[*]    //温度
[*]    private float temperature;
[*]    //湿度
[*]    private float humidity;
[*]    //气压
[*]    private float pressure;
[*]      
[*]    public WeatherData2() {
[*]      //我们的构造器不再需要为了记住观察者们而建立数据结构了
[*]    }
[*]      
[*]    public void measurementsChanged() {
[*]      //在调用notifyObservers()之前,要先调用setChanged()来表示状态已经改变
[*]      setChanged();
[*]      //注意:我们没有调用notifyobs()传送数据对象,这表示我们采用的做法是“拉”
[*]      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;
[*]    }
[*]}

view plain copy
https://code.csdn.net/assets/CODE_ico.pnghttps://code.csdn.net/assets/ico_fork.svg


[*]package observer;
[*]
[*]import java.util.Observable;
[*]import java.util.Observer;
[*]public class CurrentConditionsDisplay2 implements Observer, DisplayElement {
[*]    Observable observable;
[*]    private float temperature;
[*]    private float humidity;
[*]
[*]    public CurrentConditionsDisplay2(Observable observable) {
[*]      this.observable = observable;
[*]      observable.addObserver(this);
[*]    }
[*]
[*]    @Override
[*]    public void display() {
[*]      System.out.println("Current conditions: " + temperature + "F degress and " + humidity + "% humidity");
[*]    }
[*]
[*]    @Override
[*]    public void update(Observable o, Object arg) {
[*]      if(o instanceof WeatherData2) {
[*]            WeatherData2 weatherData2 = new WeatherData2();
[*]            this.temperature = weatherData2.getTemperature();
[*]            this.humidity = weatherData2.getHumidity();
[*]            display();
[*]      }
[*]    }
[*]
[*]    public static void main(String[] args) {
[*]      WeatherData2 weatherData2 = new WeatherData2();
[*]      CurrentConditionsDisplay2 conditionsDisplay2 = new CurrentConditionsDisplay2(weatherData2);
[*]      weatherData2.setMeasurements(80, 65, 30.4f);
[*]      weatherData2.setMeasurements(82, 70, 29.2f);
[*]      weatherData2.setMeasurements(78, 90, 30.4f);
[*]    }
[*]}

注意:
java.util.Observable的黑暗面:1、可观察者是一个“类”而不是一个“接口”,更糟糕的是,他甚至没有实现一个接口,不幸的是,java.util.Observable的实现有许多问题,限制了他的使用和复用。2、Observable是一个类,你必须设计一个类继承他,如果某类想同时具有Observable类和另一个超类的行为,就会陷入两难,毕竟Java不支持多继承,这就限制了Observable的复用潜力3、看ObservableAPI可以发现setChanged()方法被保护起来了(定义成了protected),意味着:除非你继承自Observable,否则你无法创建Observable实例并组合到你自己的对象中来,这就违反了设计原则:“多用组合,少用继承”。

七、要点总结:1、观察者模式定义了对象之间一对多的关系。2、主题(也就是可观察者也称抽象目标角色)用一个共同的接口来更新观察者。3、观察者和可观察者之间用松耦合方式结合,可观察者不知道观察者的细节,只知道观察者实现了观察者接口。4、使用此模式时,你可以从被观察者处“推(push)”或“拉(pull)”数据(个人感觉“推”的方式更“正确”)5、有多个观察者时,不可以依赖特定的通知次序。6、Java有多中观察者模式的实现,包括了通用的java.util.Observable7、要注意java.util.Observable实现上所带来的一些问题8、如果有必要的话,可以实现自己的Observable,这并不难,不要害怕。9、Swing大量使用观察者模式,许多GUI框架也是如此。10、此模式也被应用在许多地方,例如:JavaBeans、RMI等。

小太阳_lxy 发表于 2016-11-28 15:58

有意思!!{:1_919:}

tiancaizaizuo 发表于 2016-11-29 12:34

顶起来,好东西!

lytalyt 发表于 2016-11-30 08:46

好文受教了!

_vision 发表于 2016-11-30 09:04

很好,学习了啊

InvictusLee 发表于 2016-11-30 14:44

还有个Event_Bus框架

素雪年华 发表于 2017-1-6 11:55

一直觉得观察者模式和C#的委托很相似
页: [1]
查看完整版本: Java观察者模式