Java設計模式---------觀察者模式
以下內容主要來自《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實例並組合到你自己的對象中來,這違反了“多用組合,少用繼承”的設計原則。
Java設計模式---------觀察者模式