2010年1月23日 星期六

淺談設計模式之觀察者模式

小明是個勤奮的台科大學生,平時相當努力在做一些與課業毫不相干的事情,例如在計算機網路期末考的前一晚還在研究Java的反射機制。如此的勤奮,以至於他在學期結束時發現有半數以上的老師不打算讓他過關。就這樣,小明被二一了。被二一的小明在學期結束後恐懼於父母的鞭打,隱藏自己提早畢業的事實,藉故留在台北,打算就這樣瞞天過海不理人間世事,用心鑽研Java。不料,紙終究包不住火,某日小明在宿舍享受著Java優雅的語法時,一陣急促的敲門聲打斷了他的思緒。小明不耐煩地開了門,還沒來得及看清楚來訪者的臉之前就先被一記大腳踹到牆邊,抬頭一看才驚見自己青筋暴現面目猙獰的老爸。這時,小明才終於意會到,老爸是自己在學資料的觀察者之一,同樣會被告知自己被二一的消息...
儘管可能有人好奇小明的下場,這畢竟不是這篇文章要講述的重點,這篇文章的重點是「觀察者模式」的實作,也就是說明在物件導向程式設計中,如何實作物件與物件的註冊與通知機制,而上述的人倫悲劇即是我們要實作的情境。小明的下場如何,請看倌自行想像。

觀察者模式包含兩種角色:「觀察者(Observer)」、「被觀察者(Observable)」。觀察者與被觀察者都能夠有多個:一個「被觀察者」能夠被許多「觀察者」觀察,一個「觀察者」也能一次觀察多個「被觀察者」。「觀察者」必須向「被觀察者」註冊,「被觀察者」在狀態改變或某些條件滿足時則告知「觀察者」,如下圖所示。


觀察者必須提供一個方法(update)讓被觀察者在狀態更新時呼叫,被觀察者也必須提供觀察者註冊(addObserver)及移除(removeObserver)的方法,如下介面所定義:
interface Observer{
/** 通知此觀察者*/
public void update(String message);
}

interface Observable{
/** 增加觀察者 */
public void addObserver(Observer observer);
/** 移除觀察者*/
public void removeObserver(Observer observer);
}
Observable介面的addObserver方法雖暗示了實作類別必須使用陣列或串列來儲存多個觀察者實體,但這並非強制性的,一個被觀察者也能只有一個觀察者,取決於整體的設計。Observer介面中的update方法所傳遞的參數也取決於使用的場合。若要設計出較一般化的Observer模式的實作,可以參考Java API中的Observer介面和Observable類別。

在實作上述情境前,
我們先整理出幾個步驟:首先,我們將「小明」與「小明的爸爸」註冊為「小明在學資料」的觀察者。其次,設定「小明在學資料」裡的分數。然後,學期結束時,「小明在學資料」會結算被當科目,確認小明有沒有被二一。最後,學期結束時,「小明的在學資料」會自動通知「小明」與「小明的爸爸」,小明是否有被二一。
以下是主程式:
class Main{
public static void main(String[] arg){
//小明的在學資料
StudentData studentData = new StudentData("小明");

People ming = new People("小明本人");
People father = new People("小明的爸爸");
//學生資料加入觀察者
studentData.addObserver(ming);
studentData.addObserver(father);

//設定成績
studentData.setScore("微積分", 59);
studentData.setScore("物理", 60);
studentData.setScore("計算機網路", 54);

// 學期結束,結算被當科目
studentData.validateCredits();
}
}
接下來是表示人(小明本人、小明的爸爸)的類別,實作觀察者介面:
class People implements Observer{
private String name;

public People(String name){
this.name = name;
}

public void update(String message){
System.out.println(
name + " 收到["+ message +"]的訊息。");

}
}
最後是表示「學生在學資料」的類別,實作被觀察者介面:
/** 在學學生資料*/
class StudentData implements Observable{
private String name;
/** 使用LinkedList來儲存多個觀察者。 */
private List< Observer> observers =
new LinkedList<Observer >();
private HashMap< String,Integer> scores =
new HashMap<String, Integer>();

public StudentData(String name){
this.name = name;
}

public String getName(){
return name;
}
/** 設定成績*/
public void setScore(String subject, int score){
scores.put(subject, score);
}

/** 結算學分*/
public void validateCredits(){
int totalSubject = scores.size(); //全部科目數
int failSubject = 0; //被當的科目數

/** 尋訪整個HashMap,取出所有的值 */
for(int score: scores.values()){
//不到60分,累加被當的科目數
if( score < 60)
failSubject++;
}
//若被當的科目數大於總科目數的一半
if( (totalSubject / 2) < failSubject )
notifyObservers("下學期不用繳註冊費。");
else
notifyObservers("下學期記得繳註冊費。");

}

/** 通知所有觀察者*/
public void notifyObservers(String msg){
/** 尋訪整個List,取出所有觀察者 */
for(Observer observer : observers)
observer.update(msg);
}
/** 新增觀察者*/
public void addObserver(Observer observer){
observers.add(observer);
}
/** 移除觀察者*/
public void removeObserver(Observer observer){
observers.remove(observer);
}
}
執行結果如下:
小明本人 收到[下學期不用繳註冊費。]的訊息。
小明的爸爸 收到[下學期不用繳註冊費。]的訊息。

Observer模式的原理其實很簡單:只要在被觀察者的狀態被改變(ex.set方法被呼叫)或條件滿足時,經由Observer介面所定義的方法,將狀態改變的消息傳遞給已註冊的觀察者們。然而,Observer模式有相當多應用,例如著名的MVC架構模式即是根基於此模式。關於Observer模式的各種應用,將在之後的文章提到,請耐心等待。

沒有留言:

張貼留言