標籤:
定義對象見的一種一對多的依賴關係,當一個對象的狀態發生改變時,所有依賴於它的對象都得到通知並被自動更新。
觀察者模式的角色
1. 抽象主題角色(Subject):把所有對觀察者對象的引用儲存在一個集合中,每個抽象主題角色都可以有任意數量的觀察者。抽象主題提供一個介面,可以增加和刪除觀察者角色。一般用一個抽象類別和介面來實現。
2. 具體主題角色(ConcreteSubject):在具體主題內部狀態改變時,給所有登記過的觀察者發出通知。具體主題角色通常用一個子類實現。
3. 抽象觀察者角色(Observer):為所有的管擦著定義一個介面,在得到主題的通知更新自己。
4. 具體觀察者角色(ConcreteObserver):該角色實現抽象觀察者角色所要求的更新介面,以便使本身的狀態和主題的狀態相協調。通常用一個子類實現。如果需要,具體觀察者角色可以保持一個指向具體主題角色的引用。
舉個簡單案例:
1 抽象觀察者角色(Observer):
public interface Observer{ public void update(String str);}
2 抽象主題角色(Subject)
public abstract class Subject{ private List<Observer> list = new ArrayList<>(); public void attach(Observer observer) { list.add(observer); } public void detach(Observer observer) { list.remove(observer); } public void notifyObservers(String str) { int len = list.size(); for(int i=0;i<len;i++) { list.get(i).update(str); } }}
3 具體主題角色(ConcreteSubject)
public class ConcreteSubject extends Subject{ private String subjectState; public String getState(){ return subjectState; } public void setState(String newState) { this.subjectState = newState; System.out.println("主題狀態為: "+subjectState); this.notifyObservers(subjectState); }}
4 具體觀察者角色(ConcreteObserver)
public class ConcreteObserver implements Observer{ private String observerState; @Override public void update(String state) { observerState = state; System.out.println("狀態為: "+observerState); }}
- 測試代碼:
ConcreteSubject subject = new ConcreteSubject(); Observer observer = new ConcreteObserver(); subject.attach(observer); subject.setState("new State");
輸出結果:
主題狀態為: new State狀態為: new State
推模型和拉模型
在觀察者模式中,又分為推模型和拉模型兩種方式。
1. 推模型:主題對象向觀察者推送主題的詳細資料,不管觀察者是否需要,推送的資訊通常是主題對象的全部或部分資料。
2. 拉模型:主題對象在通知觀察者的時候,只傳遞少量資訊。如果觀察者需要更具體的資訊,由觀察者主動到主題對象中擷取,相當於是觀察者從主題對象中拉資料。一般這種模型的實現中,會把主題對象自身通過update()方法傳遞給觀察者,這樣在觀察者需要擷取資料的時候,就可以通過這個引用來擷取了。
將上面的代碼修改一下,變為拉模型案例:
1 抽象觀察者角色Observer2,通常是把主題對象當做參數傳遞
public interface Observer2{ public void update(Subject2 subject);}
2 具體觀察者角色
public class ConcreteObserver2 implements Observer2{ private String observerState; @Override public void update(Subject2 subject) { observerState = ((ConcreteSubject2)subject).getState(); System.out.println("狀態為: "+observerState); }}
3 抽象主題角色
public abstract class Subject2{ private List<Observer2> list = new ArrayList<>(); public void attach(Observer2 observer) { list.add(observer); } public void detach(Observer2 observer) { list.remove(observer); } public void notifyObservers() { int len = list.size(); for(int i=0;i<len;i++) { list.get(i).update(this); } }}
4 具體主題角色
public class ConcreteSubject2 extends Subject2{ private String subjectState; public String getState(){ return subjectState; } public void setState(String newState) { this.subjectState = newState; System.out.println("主題狀態為: "+subjectState); this.notifyObservers(); }}
測試代碼:
ConcreteSubject2 subject = new ConcreteSubject2(); Observer2 observer = new ConcreteObserver2(); subject.attach(observer); subject.setState("new State");
輸出結果:(同上一個案例)
兩種模式比較:
- 推模型假定主題對象知道觀察者需要的資料;而拉模型是主題對象不知道觀察者具體需要什麼資料,沒有辦法的情況下,乾脆把自身傳遞給觀察者,讓觀察者自己取按需要取值。
- 推模型可能會使得觀察者對象難以複用,因為觀察者的update()方法是按需要定義的參數,可能無法兼顧沒有考慮到的使用方式。這就意味著出現新情況的時候,就可能提供新的update()方法,或者是乾脆重新實現觀察者;而拉模型就不會造成這樣的情況,因為拉模型下,update()方法的參數是主題對象本身,這基本上是主題對象能傳遞的最大資料集合了,基本上可以適應各種情況的需要。
優缺點
優點:
1. Subject和Observer之間是松耦合的,分別可以各自獨立改變。
2. Subject在發送廣播通知的時候,無需指定具體的Observer,Observer可以自己決定是否要訂閱Subject的通知。
缺點:
1. 鬆柔和導致代碼關係不明顯,有時可能難以理解
2. 如果一個Subject被大量Observer訂閱的話,在廣播通知的時候可能會有效率問題。
適用情境
1. 對一個對象狀態的更新,需要其他對象同步更新,而且其他對象的數量動態改變。
2. 對象僅需要將自己的更新通知給其他對象而不需要知道其他對象的細節。
JDK中的觀察者模式:
java.util.EventListener
javax.servlet.http.HttpSessionBindingListener
javax.servlet.http.HttpSessionAttributeListener
javax.faces.event.PhaseListener
參考資料
1. 《23種設計模式》
2. 《細數JDK裡的設計模式》
3. 《《JAVA與模式》之觀察者模式》
設計模式:觀察者模式(Observer)