一、模式的定義與特點
觀察者(Observer)模式的定義:觀察者模式又被稱為發布-訂閱/模型-視圖模式,屬於行為型
設計模式的一種,是一個在項目中經常使用的模式。指多個對象間存在一對多的依賴關系,當一個對象的狀態發生改變時,所有依賴於它的對象都得到通知並被自動更新。
二、觀察者模式優缺點
觀察者模式是一種對象行為型模式,其主要優點如下:
- 降低了目標與觀察者之間的耦合關系,兩者之間是抽象耦合關系。
- 目標與觀察者之間建立了一套觸發機制。
它的主要缺點如下:
- 目標與觀察者之間的依賴關系並沒有完全解除,而且有可能出現循環引用。
- 當觀察者對象很多時,通知的發布會花費很多時間,影響程序的效率。
三、觀察者模式的實現
實現觀察者模式時要注意具體目標對象和具體觀察者對象之間不能直接調用,否則將使兩者之間緊密耦合起來,這違反了面向對象的設計原則。
觀察者模式的主要角色如下。
- 抽象主題(Subject)角色:也叫抽象目標類,它提供了一個用於保存觀察者對象的聚集類和增加、刪除觀察者對象的方法,以及通知所有觀察者的抽象方法。
- 具體主題(Concrete Subject)角色:也叫具體目標類,它實現抽象目標中的通知方法,當具體主題的內部狀態發生改變時,通知所有注冊過的觀察者對象。
- 抽象觀察者(Observer)角色:它是一個抽象類或接口,它包含了一個更新自己的抽象方法,當接到具體主題的更改通知時被調用。
- 具體觀察者(Concrete Observer)角色:實現抽象觀察者中定義的抽象方法,以便在得到目標的更改通知時更新自身的狀態。
觀察者模式的結構圖如圖所示:
代碼如下:
public class ObserverPattern { public static void main(String[] args) { Subject subject=new ConcreteSubject(); Observer obs1=new ConcreteObserver1(); Observer obs2=new ConcreteObserver2(); subject.add(obs1); subject.add(obs2); subject.notifyObserver(); } } //抽象目標 abstract class Subject { protected List<Observer> observers=new ArrayList<Observer>(); //增加觀察者方法 public void add(Observer observer) { observers.add(observer); } //刪除觀察者方法 public void remove(Observer observer) { observers.remove(observer); } public abstract void notifyObserver(); //通知觀察者方法 } //具體目標 class ConcreteSubject extends Subject { public void notifyObserver() { System.out.println("具體目標發生改變..."); System.out.println("--------------"); for(Object obs:observers) { ((Observer)obs).response(); } } } //抽象觀察者 interface Observer { void response(); //反應 } //具體觀察者1 class ConcreteObserver1 implements Observer { public void response() { System.out.println("具體觀察者1作出反應!"); } } //具體觀察者1 class ConcreteObserver2 implements Observer { public void response() { System.out.println("具體觀察者2作出反應!"); } }
測試結果為:
具體目標發生改變... -------------- 具體觀察者1作出反應! 具體觀察者2作出反應!
四、觀察者模式的應用實例
接下來再看一個關於上下課打鈴,老師同學上下課的示例:
public class BellEventTest { public static void main(String[] args) { BellEventSource bell=new BellEventSource(); //鈴(事件源) bell.addPersonListener(new TeachEventListener()); //注冊監聽器(老師) bell.addPersonListener(new StuEventListener()); //注冊監聽器(學生) bell.ring(true); //打上課鈴聲 System.out.println("------------"); bell.ring(false); //打下課鈴聲 } } //鈴聲事件類:用於封裝事件源及一些與事件相關的參數 // EventObject: The root class from which all event state objects shall be derived. class RingEvent extends EventObject { private static final long serialVersionUID=1L; private boolean sound; //true表示上課鈴聲,false表示下課鈴聲 public RingEvent(Object source,boolean sound) { super(source); this.sound=sound; } public void setSound(boolean sound) { this.sound=sound; } public boolean getSound() { return this.sound; } } //目標類:事件源,鈴 class BellEventSource { private List<BellEventListener> listener; //監聽器容器 public BellEventSource() { listener=new ArrayList<BellEventListener>(); } //給事件源綁定監聽器 public void addPersonListener(BellEventListener ren) { listener.add(ren); } //事件觸發器:敲鍾,當鈴聲sound的值發生變化時,觸發事件。 public void ring(boolean sound) { String type=sound?"上課鈴":"下課鈴"; System.out.println(type+"響!"); RingEvent event=new RingEvent(this, sound); notifies(event); //通知注冊在該事件源上的所有監聽器 } //當事件發生時,通知綁定在該事件源上的所有監聽器做出反應(調用事件處理方法) protected void notifies(RingEvent e) { BellEventListener ren=null; Iterator<BellEventListener> iterator=listener.iterator(); while(iterator.hasNext()) { ren=iterator.next(); ren.heardBell(e); } } } //抽象觀察者類:鈴聲事件監聽器 EventListener:A tagging interface that all event listener interfaces must extend. interface BellEventListener extends EventListener { //事件處理方法,聽到鈴聲 public void heardBell(RingEvent e); } //具體觀察者類:老師事件監聽器 class TeachEventListener implements BellEventListener { public void heardBell(RingEvent e) { if(e.getSound()) { System.out.println("老師上課了..."); } else { System.out.println("老師下課了..."); } } } //具體觀察者類:學生事件監聽器 class StuEventListener implements BellEventListener { public void heardBell(RingEvent e) { if(e.getSound()) { System.out.println("同學們,上課了..."); } else { System.out.println("同學們,下課了..."); } } }
測試結果為:
上課鈴響! 老師上課了... 同學們,上課了... ------------ 下課鈴響! 老師下課了... 同學們,下課了...
五、觀察者模式的應用場景
通過前面的分析與應用實例可知觀察者模式適合以下幾種情形。
- 對象間存在一對多關系,一個對象的狀態發生改變會影響其他對象。
- 當一個抽象模型有兩個方面,其中一個方面依賴於另一方面時,可將這二者封裝在獨立的對象中以使它們可以各自獨立地改變和復用。
六、觀察者模式的擴展
在 Java 中,通過 java.util.Observable 類和 java.util.Observer 接口定義了觀察者模式,只要實現它們的子類就可以編寫觀察者模式實例。
1、Observable類
Observable 類是抽象目標類,它有一個 Vector 向量,用於保存所有要通知的觀察者對象,下面來介紹它最重要的 3 個方法。
- void addObserver(Observer o) 方法:用於將新的觀察者對象添加到向量中。
- void notifyObservers(Object arg) 方法:調用向量中的所有觀察者對象的 update。方法,通知它們數據發生改變。通常越晚加入向量的觀察者越先得到通知。
- void setChange() 方法:用來設置一個 boolean 類型的內部標志位,注明目標對象發生了變化。當它為真時,notifyObservers() 才會通知觀察者。
2、Observer 接口
Observer 接口是抽象觀察者,它監視目標對象的變化,當目標對象發生變化時,觀察者得到通知,並調用 void update(Observable o,Object arg) 方法,進行相應的工作。
我們通過利用 Observable 類和 Observer 接口實現原油期貨的觀察者模式,其結構圖如圖所示:
分析:當原油價格上漲時,空方傷心,多方局興;當油價下跌時,空方局興,多方傷心。本實例中的抽象目標(Observable)類在 Java 中已經定義,可以直接定義其子類,即原油期貨(OilFutures)類,它是具體目標類,該類中定義一個 SetPriCe(float price) 方法,當原油數據發生變化時調用其父類的 notifyObservers(Object arg) 方法來通知所有觀察者;另外,本實例中的抽象觀察者接口(Observer)在 Java 中已經定義,只要定義其子類,即具體觀察者類(包括多方類 Bull 和空方類 Bear),並實現 update(Observable o,Object arg) 方法即可。
代碼實現如下:
public class CrudeOilFutures { public static void main(String[] args) { OilFutures oil=new OilFutures(); Observer bull=new Bull(); //多方 Observer bear=new Bear(); //空方 oil.addObserver(bull); oil.addObserver(bear); oil.setPrice(10); oil.setPrice(-8); } } //具體目標類:原油期貨 class OilFutures extends Observable { private float price; public float getPrice() { return this.price; } public void setPrice(float price) { super.setChanged() ; //設置內部標志位,注明數據發生變化 super.notifyObservers(price); //通知觀察者價格改變了 this.price=price ; } }
//具體觀察者類:多方 class Bull implements Observer { public void update(Observable o,Object arg) { Float price=((Float)arg).floatValue(); if(price>0) { System.out.println("油價上漲"+price+"元,多方高興了!"); } else { System.out.println("油價下跌"+(-price)+"元,多方傷心了!"); } } } //具體觀察者類:空方 class Bear implements Observer { public void update(Observable o,Object arg) { Float price=((Float)arg).floatValue(); if(price>0) { System.out.println("油價上漲"+price+"元,空方傷心了!"); } else { System.out.println("油價下跌"+(-price)+"元,空方高興了!"); } } }
測試結果如下:
油價上漲10.0元,空方傷心了!
油價上漲10.0元,多方高興了!
油價下跌8.0元,空方高興了!
油價下跌8.0元,多方傷心了!