Observer Pattern(觀察者模式)是屬於設計模式中Behavioral Pattern(行為模式)。當物件(object)間的有一對多(one-to-many)關係時,可利用Observer Pattern來設計。
例如當一個物件的狀態(state)發生變化時,希望所相依的所有物件都可以自動收到通知的狀況。
在GoF的Design Patterns的書中對於Observer Pattern的定義:
Define a one-to-many dependency between objects so that when one object changes state, all its dependents are notified and updated automatically.
在Observer Pattern中有兩個角色,一為觀察者(Observer,也可稱為Listener),一為被觀察對象(Observable,也可稱為Subject)。
通常會以現實生活中讀者(Reader)訂閱新聞(News)的例子來解釋Observer Pattern。訂閱新聞時,讀者不用隨時主動地去檢查是否有新的新聞,因為這樣太麻煩了,比較聰明的方法是當有新的新聞時,新聞會自動通知所有訂閱的讀者。在訂閱新聞的例子中,讀者就是Observer,新聞就是Observable,有很多的讀者訂閱某一新聞,所以是一對多關係。
另一個例子就是通訊軟體(例如LINE),你不用隨時檢查群組中是否有新的對話訊息,而是當群組中有新的訊息,你會自動收到通知。加入(訂閱)群組的人很多,而群組只有一個,這就是典型的一對多關係。
在Observer Pattern中,Observer及Observable分別需要實作以下方法。(若您是第一次學習什麼是Observer Pattern,對於下面各方法的解釋看過就好,因為了解設計模式最好還是看範例才容易理解)
Observer物件要有以下三種方法:
- 可被Observable呼叫的方法(method)。當Observable的狀態改變時,Observable使用此方法來通知Observer。
- 訂閱(subscribe)Observable的方法。
- 取消訂閱Observable的方法。
而Observable物件有下面三種方法:
- 註冊(register)Observer的方法。當Observer訂閱Observable時,Observable使用此方法來註冊Observer。
- 取消註冊Observer的方法。當Observer取消訂閱Observable時,Observable使用此方法來取消註冊Observer。
- 通知Observer的方法。當Observable狀態改變時,用來通知所有的Observer。
以下為讀者訂閱新聞的範例程式,強烈建議您打開IDE親手打一打下面的範例。
程式目錄結構
首先要建立兩個Interface,Observable/Subject(被觀察對象)及Observer/Listener(觀察者)。
Observable.java
package idv.matt.observable;
import idv.matt.observer.Observer;
public interface Observable {
/**
* 註冊Observer(Reader)
*/
public void register(Observer reader);
/**
* 取消註冊Observer(Reader)
*/
public void unregister(Observer reader);
/**
* 通知Observer(Reader)
*/
public void inform();
}
Observable
定義了以下方法:
- 註冊
Observer
的方法register()
, - 取消註冊
Observer
的方法unregister()
, - 通知所有
Observer
的方法notify()
。
Observer.java
package idv.matt.observer;
import idv.matt.observable.Observable;
public interface Observer {
/**
* 訂閱Observable(News)
*/
void subscribe(Observable news);
/**
* 取消訂閱Obervable(News)
*/
void unsubscribe();
/**
* 給Observable(News)呼叫的方法
*/
void update();
}
Observer
定義了以下方法:
- 訂閱
Observable
的方法subscribe()
, - 取消訂閱
Observable
的方法unsubscribe()
, Observable
用來通知Observer
更新的方法update()
。
實作Observable
的新聞類別News
News.java
package idv.matt.observable;
import java.util.ArrayList;
import idv.matt.observer.Observer;
public class News implements Observable {
private ArrayList<Observer> readerList = new ArrayList<Observer>(); // 紀錄已註冊的Readers
private boolean latestNews = true; // News的狀態
public boolean isLatestNews() {
return latestNews;
}
/**
* 設定News狀態
*/
public void setLatestNews(boolean latestNews) {
this.latestNews = latestNews;
inform();
}
/**
* 註冊Reader
*/
@Override
public void register(Observer reader) {
readerList.add(reader);
}
/**
* 取消註冊Reader
*/
@Override
public void unregister(Observer reader) {
readerList.remove(reader);
}
/**
* 通知全部的Reader
*/
@Override
public void inform() {
for(Observer reader : readerList){
reader.update();
}
}
}
News
實作Observable
介面,為被觀察對象:
- 成員變數
readerList
中記錄了所有的訂閱讀者,任一讀者只要訂閱了新聞,即會被加入readerList
。 - 成員變數
latestNews
代表News
的狀態,若為true表示最新,反之為舊的新聞。setLatestNews()
被呼叫時會更改News
的狀態,進而執行inform()
方法去通知讀者名單readerList
中的所有讀者。 - 註冊方法
regsiter()
,當讀者訂閱新聞時,該讀者會被加入讀者名單readerList
。 - 取消註冊方法
unregister()
,當讀者取消訂閱時,該讀者會從讀者名單readerList
中移除。 - 通知方法
inform()
,在News
的狀態更動時,會被呼叫來通知並更新所有的讀者。
實作Observer
的讀者類別Reader
。
Reader.java
package idv.matt.observer;
import idv.matt.observable.News;
import idv.matt.observable.Observable;
public class Reader implements Observer {
private String name = null; // 讀者的名稱
private Observable news = null; // 訂閱的News
public Reader(String name){
this.name = name;
}
/**
* 訂閱News
*/
@Override
public void subscribe(Observable news) {
this.news = news;
news.register(this); // News註冊此Reader
}
/**
* 取消訂閱News
*/
@Override
public void unsubscribe() {
news.unregister(this); // News取消註冊此Reader
}
/**
* Observer(News)的更新通知
*/
@Override
public void update() {
read(); // 當Reader被News通知時會執行read()
}
private void read(){
if(((News) this.news).isLatestNews()){ // 檢查新聞的狀態
System.out.println(name + " read latest news");
} else {
System.out.println(name + " read old news");
}
}
}
Reader
實作Observer
介面,為觀察者
- 更新方法
update()
,當News
的狀態更新時,News
會藉由此方法通知Reader
。 - 訂閱方法
subscribe()
,用來訂閱News
。訂閱後則此讀者會被新聞註冊。 - 取消訂閱方法
unsubscribe()
,用來取消訂閱News
,取消訂閱後則此讀者會被新聞取消註冊。 - 閱讀方法
read()
,依照News
的狀態來閱讀新聞。
範例的類別圖
最後是測試用的Main
Main.java
package idv.matt.main;
import idv.matt.observable.News;
import idv.matt.observer.Reader;
public class Main {
public static void main(String[] args) {
News news = new News(); // 建立一個新聞(Observable)
Reader john = new Reader("Matt"); // 建立一些讀者
Reader mary = new Reader("John");
Reader bill = new Reader("Lisa");
// 每個讀者訂閱新聞
// Observer(Reader)訂閱Observable(News)
john.subscribe(news); //訂閱新聞
mary.subscribe(news);
bill.subscribe(news);
news.setLatestNews(true); // 更新Observble(News)的狀態
}
}
執行結果
Matt read latest news
John read latest news
Lisa read latest news
在Main
中當news
的狀態因呼叫setLatestNews()
而改變時,所有的讀者都被通知。
若讀者John呼叫unsubscribe()
方法取消訂閱新聞,則之後news
狀態更動時便不會被通知。
除了Reader
類別外,如有其他類別(例如記者(Journalist
)類別或是作家(Writer
)類別)也需要在News
狀態改變時也被通知,只要實作Observer
介面並訂閱News
即可。
2 則留言:
謝謝你提供的範例,講解的非常清楚。
問個小問題,Reader在unsubscribe的時候,有沒有需要把news設成null呢?
Hi 樓上,這邊的設計一個Reader只能訂閱一種News,在subscribe()時會更新this.news,所以unsubscribe()時不用特地把this.news設為null。但如果你想要設為null也可,記得放在new.unregister(this)後面。
張貼留言