以下内容主要来自《HeadFirst设计模式》一书和博文:http://www.cnblogs.com/xrq730/p/4908686.html,仅作为个人的学习笔记使用。
观察者模式
定义了对象之间的一对多依赖,当一个对象改变状态时,它的所有依赖者都会收到通知并自动更新。
观察者模式的类图
设计原则:努力实现交互对象之间的松耦合设计
当两个对象之间松耦合时,它们依然可以交互,但是不太清楚彼此的细节。观察者模式提供了一种对象设计,让主题和观察者之间松耦合。主题只需要知道观察者实现了Observer接口,主题不需要知道观察者的具体类是谁,做了些什么或者其他细节。改变主题或者观察者其中的一方,并不会影响另一方,因为两者是松耦合的。
下面的以气象站为例来阐述观察者模式:
需要建立一个WeatherData气象站,WeatherData对象负责追踪目前的天气状况(温度Temperature、湿度Hunidity、气压Pressure),同时建立三种布告板,分别显示当前的状况、气象统计及简单的预报。
在此例中,WeatherData相当于主题,当其发生变化时,会通知这三种布告板进行相应的显示。
具体的代码实现
1.主题类
package Test; public interface Subject { public abstract void registerObserver(Observers observer); public abstract void removeObserver(Observers observer); public abstract void notifyObservers(); }
2. 观察者接口
package Test; public interface Observers { public abstract void update(float temp,float humidity,float pressure); }
3.具体的主题类
package Test; import java.util.ArrayList; import java.util.List; public class WeatherData implements Subject{ List<Observers> observerList; private float temperature; private float humidity; private float pressure; public WeatherData(){ observerList=new ArrayList<>();//创建了一个列表用来存储所有的观察者 }
public void measurementsChanged(){ notifyObservers(); } public void setWeatherData(float temperature,float humidity,float pressure){ this.temperature=temperature; this.humidity=humidity; this.pressure=pressure; measurementsChanged(); }
@Override public void registerObserver(Observers observer) { if(!observerList.contains(observer)){ observerList.add(observer); } } @Override public void removeObserver(Observers observer) { //首先要判断一下在列表中确实存在此对象 int i=observerList.indexOf(observer); if(i>=0){ observerList.remove(i); } } @Override public void notifyObservers() { for(Observers observer:observerList){ observer.update(temperature,humidity,pressure); } } }
4.具体的观察者对象
package Test; public class CurrentConditionsDisplay implements Observers, DisplayElement{ private float temperature; private float humidity; private float pressure; private Subject weatherData; public CurrentConditionsDisplay(Subject sb) { weatherData=sb; weatherData.registerObserver(this); } @Override public void update(float temp,float humidity,float pressure) { this.temperature=temp; this.humidity=humidity; this.pressure=pressure; display(); } @Override public void display() {//实现了DisplayElement接口中的方法 System.out.println("Observer:"+this+",Temp:"+temperature+",Humidity:"+humidity+",Pressure:"+pressure); } }
5.测试类:
package Test; public class ObserverModeTest { public static void main(String[] args){ WeatherData weatherData=new WeatherData(); CurrentConditionsDisplay cb1=new CurrentConditionsDisplay(weatherData); weatherData.setWeatherData(80,65,30.4f); } }
输出:
Observer:Test.CurrentConditionsDisplay@404b9385,Temp:80.0,Humidity:65.0,Pressure:30.4
主题中的 setWeatherData()方法调用了notifyAllObservers()方法,然后notifyAllObservers()方法中调用了所有观察者的update()方法来通知所有观察者。
观察者模式的两种模型
1、推模型
主题向观察者推送主题的详细信息,不管观察者是否需要。推送的信息通常是主题对象的全部或者部分数据,上面的例子中在notify()方法中直接调用各个观察者对的update(float temperature, float humidity, float pressure)方法,直接将数据通知给了所有观察者,就是使用的推模型。
2、拉模型
主题对象字啊通知观察者的时候,只传递少量的信息。如果观察者需要更加具体的信息,由观察者主动到主题对象中去获取。相当于是观察者主动从主题对象中拉数据。一般这种模型的实现中,会把主题对象自身通过update()方法传递给观察者,并提供相应数据的public修饰的get方法。
优势:1. 如果有观察者只需要一点点数据,就不会被迫收到一堆数据。
2. 如果某天决定对主题进行功能扩展,新增更加的数据,就不用修改和更新对每位观察者的调用update(..)方法,只需要改变自己来允许更多的getter方法来获取新增的数据。
对于拉模型,我们直接使用Java内置的观察者模式为例进行分析:
观察者模式在Java中的应用及解读
1. Java.util.Observable类是主题类父类,这是一个线程安全类,因为存储观察者的数据结构是用Vector实现的。
public class Observable { private boolean changed = false; private Vector obs; /** Construct an Observable with zero Observers. */ public Observable() { obs = new Vector(); } /** * Adds an observer to the set of observers for this object, provided * that it is not the same as some observer already in the set. * The order in which notifications will be delivered to multiple * observers is not specified. See the class comment. * * @param o an observer to be added. * @throws NullPointerException if the parameter o is null. */ public synchronized void addObserver(Observer o) { if (o == null) throw new NullPointerException(); if (!obs.contains(o)) { obs.addElement(o); } } ... }
主题对象中有这些方法对观察者进行操作:
方 法 | 作 用 |
addObserver(Observer o) | 如果观察者与集合中已有的观察者不同,则向对象的观察者集合中添加此观察者 |
clearChanged()、hasChanged()、setChanged() | 这三个方法算是一对,用来标记此观察者对象(主题对象)是否被改变的状态的 |
countObservers() | 返回观察者对象的数目 |
deleteObserver(Observer o) | 从对象的观察者集合中删除某个观察者 |
deleteObservers() | 清除观察者列表 |
notifyObservers()、notifyObservers(Object arg) | 如果本对象有变化则通知所有等级的观察者,调用update()方法 |
2.Java.util.Observer接口。
所有的观察者都需要实现这个接口。
public interface Observer { /** * This method is called whenever the observed object is changed. An * application calls an <tt>Observable</tt> object‘s * <code>notifyObservers</code> method to have all the object‘s * observers notified of the change. * * @param o the observable object. * @param arg an argument passed to the <code>notifyObservers</code> * method. */ void update(Observable o, Object arg); }
这个update()方法中的第一个参数即为主题对象自身,可以用于观察者通过此索引和相应的get方法获取其所需的数据。
具体的代码实现:
package Test; import java.util.Observable; public class WeatherData2 extends Observable { private float temperature; private float humidity; private float pressure; public void measurementsChanged(){ setChanged(); notifyObservers();//直接继承,不用再进行实现,这个方法内部会调用各个观察者的update(..)方法 } public void setWeatherData(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; } }
1.主题对象继承Observable类之后不用再创建数据结构存储观察者,其空构造方法会先调用父类的空构造方法,在父类的构造方法内部会将观察者存入Vector进行注册;
2. 不用再对notifyObservers()方法进行实现,这个方法内部会调用各个观察者的update(..)方法,在notifyObservers()内部没有传送数据对线,这表示我们采用“拉”的模式。
package Test; import java.util.Observable; import java.util.Observer; public class CurrentConditionsDisplay2 implements Observer,DisplayElement { Observable observable; //在此类中存储主题对象索引的原因是为了以后删除观察者时方便操作 private float temperature; private float humidity; private float pressure; public CurrentConditionsDisplay2(Observable observable){ this.observable=observable; observable.addObserver(this); } @Override public void update(Observable o, Object arg) { if(o instanceof WeatherData2){ WeatherData2 weatherData=(WeatherData2)o; this.temperature=weatherData.getTemperature(); this.humidity=weatherData.getHumidity(); this.pressure=weatherData.getPressure(); display(); } } @Override public void display() { System.out.println("Current conditions:"+temperature+"F degrees and "+humidity+"% humidity"); } }
测试类:
package Test; public class ObserverModeTest { public static void main(String[] args){ WeatherData2 weatherData=new WeatherData2(); CurrentConditionsDisplay2 cb1=new CurrentConditionsDisplay2(weatherData); weatherData.setWeatherData(80,65,30.4f); } }
输出:
观察者模式主要应用场景有:
1、对一个对象状态的更新需要其他对象同步更新
2、对象仅需要将自己的更新通知给其他对象而不需要知道其他对象的细节,如消息推送.
java.util.observable的黑暗面
在Java JDK默认的观察者模式中,主题也就是被观察者是一个类而不是一个接口。java.util.Observable的实现有很多问题限制了它的使用和复用。
1.Observable是一个类
Observable是一个类,如果某个类想同事具有Observable类和另一个超类的行为,就会陷入两难。Java不支持多重继承,这限制了Observable的复用潜力。
因为没有Observable接口,所以你无法建立自己的实现,和Java内置的ObserverAPI搭配使用,也无法将java.util的实现换成另一套的实现。???
2.Observable将关键的方法保护起来
ObservableAPI的setChanged()方法被保护起来了,这意味着,除非你继承自Observable,否则你无法创建Observable实例并组合到你自己的对象中来,这违反了“多用组合,少用继承”的设计原则。