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
即可。
謝謝你提供的範例,講解的非常清楚。
回覆刪除問個小問題,Reader在unsubscribe的時候,有沒有需要把news設成null呢?
Hi 樓上,這邊的設計一個Reader只能訂閱一種News,在subscribe()時會更新this.news,所以unsubscribe()時不用特地把this.news設為null。但如果你想要設為null也可,記得放在new.unregister(this)後面。
回覆刪除