很多時候,對於一個設計來說(軟體上的,建築上的,或者它他工業上的),經驗是至關重要的。好的經驗給我們以指導,並節約我們的時間;壞的經驗則給我們以借鑒,可以減少失敗的風險。然而,從知識層面上來講,經驗只是作為一種工作的積累而存在於個人的大腦中的,很難被傳授或者記錄。為瞭解決這樣的問題,人們提出了所謂的模式的概念。所謂模式,是指在一個特定背景下,反覆出現的問題解決方案。模式是經驗的文檔化。
軟體模式的概念現在比較的廣泛,涉及到分析,設計,體繫結構,編碼,測試,重構等軟體構造生命期中的各個部分。這兒主要討論的是設計模式,指的是在軟體設計過程中反覆出現的一些問題的解決方案了。不過我們一般在提到設計模式的時候,一般都是指GOF的經典書《Design Pattern--Elements of Reusable Object-Oriented Software》出現的23個模式,因而,它是具體的針對於物件導向軟體設計過程的。
從全域上看來,模式代表了一種語言,一種被文檔化的經驗,甚至是一種文化。往往很多不方便描敘,或者描敘起來很複雜的問題,用模式語言來敘說,會讓聽者產生心領神會的感覺。當然,這需要交流雙方都能夠很好地把握模式語言的含義。然而,這並不是一件容易的事情。模式在各個人的理解上往往存在差異,這篇文章旨在從一個具體的應用角度:Java類庫,來闡敘設計模式。並結合具體的例子,希望能夠加深大家對設計模式的理解。
這兒說的Java類庫,其實並沒有局限於JDK本身,還包括了一些其他的類庫中的例子,比如JAXP等(當然,下一個版本的JDK中也會包含JAXP了)。其實設計模式的思想現在應用的如此廣泛,無論在什麼樣的設計中,只要稍微大一點的設計,都可以找到很多很多設計模式的蹤跡,或者說都不可避免的用到設計模式。下面所講的設計模式,大部分都是GOF的那部經典中出現過的23個模式,然而,還有一些,比如MVC,並不屬於那裡。一般的來講,我們認為GOF的23個模式是一些中級的模式,在它下面還可以抽象出一些更為一般的低層的模式,在其上也可以通過組合來得到一些進階的模式。當然,這兒的低中高的區別,如同區別不同的語言一樣,並沒有優劣之分,僅僅是在應用程式層面上的區別。
Observer模式
Observer模式的功用,是希望兩個(或多個)對象,我們稱之為Subject和Observer,當一方的狀態發生改變的時候,另一方能夠得到通知。也就是說,作為Observer的一方,能夠監視到Subject的某個特定的狀態變化,並為之做出反應。一個簡單的例子就是:當一個使用者視圖中的資料被使用者改變後,後端的資料庫能夠得到更新,而當資料庫被其他方式更新後,使用者視圖中的資料顯示也會隨之改變。
圖一:Obverser模式的類圖
在JDK中實際上有一個對Observer模式的簡單的實現:就是類java.util.Observerable和介面java.util.Observer。java.util.Observerable類對應於Subject,而java.util.Observer就是觀察者了。JDK中並沒有把這兩個部分都設計為介面,而是讓類java.util.Observerable提供了部分的實現,簡化了許多編程的工作。當然,這也減少了一定的靈活性。
下面列出了Observer和Observeral的函數列表,及其簡單的功能說明
java.util.Observer:
public void update(Observable obs, Object obj)
java.util.Observer 介面很簡單,只定義了這一個方法,狹義的按照Observer模式的說法,Observer應該在這個方法中調用Subject的getXXX()方法來取得最新的狀態,而實際上,你可以只是在其中對Subject的某些事件進行響應。這便是Java中的代理事件模型的一個雛形--對事件進行響應。只不過,在Observer模式中將事件特定化為某個狀態/資料的改變了。
java.util.Observable
public void addObserver(Observer obs)
向Subject註冊一個Observer。也就是把這個Observer對象添加到了一個java.util.Observable內部的列表中。在JDK中對於這個列表是簡單的通過一個java.util.Vector類來實現的,而實際上,在一些複雜的Observer模式的應用中,需要把這個部分單另出來形成一個Manager類,來管理Subject和Observer之間的映射。這樣,Subject和Observer進一步的被解藕,程式也會具有更大的靈活性。
public void deleteObserver(Observer obs)
從Subject中刪除一個登入了Observer的引用。
public void deleteObservers()
從Subjec中刪除所有註冊的Observer的引用。
public int countObservers()
返回註冊在Subject中的Observer個數。
protected void setChanged()
設定一個內部的標誌以指明這個Ovserver的狀態已經發生改變。注意這是一個protected方法,也就是說只能在Observer類和其子類中被調用,而在其它的類中是看不到這個方法的。
protected void clearChanged()
清除上敘的內部標誌。它在notifyObservers()方法內部被自動的調用,以指明Subject的狀態的改變已經傳遞到Ovserver中了。
public boolean hasChanged()
確定Subject的狀態是否發生了改變。
public void notifyObservers(Object obj)
它首先檢查那個內部的標誌,以判斷狀態是否改變,如果是的話,它會調用註冊在Subject中的每個Observer的update()方法。在JDK中這個方法內部是作為synchronized來實現的,也就是如果發生多個線程同時爭用一個java.util.Observerable的notifyObservers()方法的話,他們必須按調度的等待著順序執行。在某些特殊的情況下,這會有一些潛在的問題:可能在等待的過程中,一個剛剛被加入的Observer會被遺漏沒有被通知到,而一個剛剛被刪除了的Observer會仍然收到它已經不想要了的通知。
public void notifyObservers()
等價於調用了notifyObservers(null)。
因而在Java中應用Observer就很簡單了,需要做的是:讓需要被觀察的Subject對象繼承java.util.Observerable,讓需要觀察的對象實現java.util.Observer介面,然後用java.util.Observerable的addObserver(Observer obj)方法把Observer註冊到Subject對象中。這已經完成了大部分的工作了。然後調用java.util.Observerable的notifyObservers(Object arg)等方法,就可以實現Observer模式的機理。我們來看一個簡單使用了這個模式的例子。這個例子有三個類:FrameSubject,DateSubject,FrameObject和EntryClass,FrameSubject中使用者可以設定被觀察的值,然後自動的會在FrameObject中顯示出來,DateSubject封裝被觀察的值,並且充當Observer模式中的Subject。
public class FrameSubject extends JFrame {
…………..
//因為無法使用多重繼承,這兒就只能使用對象組合的方式來引入一個
//java.util.Observerable對象了。
DateSubject subject=new DateSubject();
//這個方法轉寄添加Observer訊息到DateSubject。
public void registerObserver(java.util.Observer o){
subject.addObserver(o);
}
//資料改變,事件被觸發後調用notifyObservers()來通知Observer。
void jButton1_actionPerformed(ActionEvent e) {
subject.setWidthInfo(Integer.parseInt(jTextField1.getText()));
subject.setHeightInfo(Integer.parseInt(jTextField2.getText()));
subject.notifyObservers();
}
……………
}
public class DateSubject extends Observable {
//封裝被觀察的資料
private int widthInfo;
private int heightInfo;
public int getWidthInfo() {
return widthInfo;
}
public void setWidthInfo(int widthInfo) {
this.widthInfo = widthInfo;
//資料改變後,setChanged()必須被調用,否則notifyObservers()方法會不起作用
this.setChanged();
}
public void setHeightInfo(int heightInfo) {
this.heightInfo = heightInfo;
this.setChanged();
}
public int getHeightInfo() {
return heightInfo;
}
}
public class FrameObserver extends JFrame implements java.util.Observer {
…………..
//觀察的資料
int widthInfo=0;
int heightInfo=0;
//在update()方法中實現對資料的更新和其它必要的反應。
public void update(Observable o, Object arg) {
DateSubject subject=(DateSubject) o;
widthInfo=subject.getWidthInfo();
heightInfo=subject.getHeightInfo();
jLabel1.setText("The heightInfo from subject is: ");
jLabel3.setText(String.valueOf(heightInfo));
jLabel2.setText("The widthInfo from subject is: ");
jLabel4.setText(String.valueOf(widthInfo));
}
…………….
}
public class EntryClass {
public static void main(String[] args) {
……………..
FrameSubject frame = new FrameSubject();
FrameObserver frame2=new FrameObserver();
//在Subject中註冊Observer,將兩者聯絡在一起
frame.registerObserver(frame2);
…………..
frame.setVisible(true);
frame2.setVisible(true);
……………..
}
}
我認為在JDK中這個Observer模式的實現,對於一般的Observer模式的應用,已經是非常的足夠了的。但是一方面它用一個類來實現了Subject,另一方面它使用Vector來儲存Subject對於Observer的引用,這雖然簡化了編程的過程,但會限制它在一些需要更為靈活,複雜的設計中的應用,有時候(雖然這種情況不多),我們還不得不重新編寫新的Subject對象和額外的Manager對象來實現更為複雜的Observer模式的應用。
小結:
這一部分主要的討論了模式的概念。隨著現代軟體工業的不斷進步,軟體系統的規模的日益擴大,越來越需要對某些個不斷出現的問題進行模式化思維,以成功的經驗或者失敗的教訓來減少軟體開發失敗的風險。模式代表了一種文檔化的經驗,它為某一類的問題提供了最好(或者說很好)的解決方案,使得即使不是經驗豐富的軟體工程師,也能夠根據模式來構建相對成功的系統。本節給出的一個Obverser模式的樣本,比較好的說明了這一點。Obverser模式主要解決在對象間的狀態映射或者鏡像的問題。
參考資料
- Software Patterns: James O. Coplien, Bell Laboratories,The Hillside Group
- 設計模式:可複用物件導向軟體的基礎 機械工業出版社
- Java2類庫增補版 機械工業出版社
- 可視化物件導向建模技術 北京航天航空工業大學出版社
- JDK1.3原始碼
- UML使用者指南 機械工業出版社
關於作者
|
|
|
劉武東:武漢大學電腦學院2001級研究生。研究方向:可重用組件技術,設計模式。 |