从Java类库看设计模式
//From?http://www.uml.org.cn/j2ee/201010214.asp
?很多時候,對于一個設計來說(軟件上的,建筑上的,或者它他工業上的),經驗是至關重要的。好的經驗給我們以指導,并節約我們的時間;壞的經驗則給我們以借鑒,可以減少失敗的風險。然而,從知識層面上來講,經驗只是作為一種工作的積累而存在于個人的大腦中的,很難被傳授或者記錄。為了解決這樣的問題,人們提出了所謂的模式的概念。所謂模式,是指在一個特定背景下,反復出現的問題解決方案。模式是經驗的文檔化。
軟件模式的概念現在比較的廣泛,涉及到分析,設計,體系結構,編碼,測試,重構等軟件構造生命期中的各個部分。這兒主要討論的是設計模式,指的是在軟件設計過程中反復出現的一些問題的解決方法了。不過我們一般在提到設計模式的時候,一般都是指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。
??1?public?class?FrameSubject?extends?JFrame {??2?
??3? …………..
??4?
??5? //因為無法使用多重繼承,這兒就只能使用對象組合的方式來引入一個
??6?
??7? //java.util.Observerable對象了。
??8?
??9? DateSubject subject=new?DateSubject();
10?
11? //這個方法轉發添加Observer消息到DateSubject。
12?
13? public?void?registerObserver(java.util.Observer o){
14?
15? subject.addObserver(o);
16?
17? }
18?
19? //數據改變,事件被觸發后調用notifyObservers()來通知Observer。
20?
21? void?jButton1_actionPerformed(ActionEvent e) {
22?
23? subject.setWidthInfo(Integer.parseInt(jTextField1.getText()));
24?
25? subject.setHeightInfo(Integer.parseInt(jTextField2.getText()));
26?
27? subject.notifyObservers();
28?
29? }
30?
31? ……………
32?
33? }
34?
35? public?class?DateSubject?extends?Observable {
36?
37? //封裝被觀察的數據
38?
39? private?int?widthInfo;
40?
41? private?int?heightInfo;
42?
43? public?int?getWidthInfo() {
44?
45? return?widthInfo;
46?
47? }
48?
49? public?void?setWidthInfo(int?widthInfo) {
50?
51? this.widthInfo = widthInfo;
52?
53? //數據改變后,setChanged()必須被調用,否則notifyObservers()方法會不起作用
54?
55? this.setChanged();
56?
57? }
58?
59? public?void?setHeightInfo(int?heightInfo) {
60?
61? this.heightInfo = heightInfo;
62?
63? this.setChanged();
64?
65? }
66?
67? public?int?getHeightInfo() {
68?
69? return?heightInfo;
70?
71? }
72?
73? }
74?
75? public?class?FrameObserver?extends?JFrame?implements?java.util.Observer {
76?
77? …………..
78?
79? //觀察的數據
80?
81? int?widthInfo=0;
82?
83? int?heightInfo=0;
84?
85? //在update()方法中實現對數據的更新和其它必要的反應。
86?
87? public?void?update(Observable o, Object arg) {
88?
89? DateSubject subject=(DateSubject) o;
90?
91? widthInfo=subject.getWidthInfo();
92?
93? heightInfo=subject.getHeightInfo();
94?
95? jLabel1.setText("The heightInfo from subject is: ");
96?
97? jLabel3.setText(String.valueOf(heightInfo));
98?
99? jLabel2.setText("The widthInfo from subject is: ");
100?
101? jLabel4.setText(String.valueOf(widthInfo));
102?
103? }
104?
105? …………….
106?
107? }
108?
109? public?class?EntryClass {
110?
111? public?static?void?main(String[] args) {
112?
113? ……………..
114?
115? FrameSubject frame =?new?FrameSubject();
116?
117? FrameObserver frame2=new?FrameObserver();
118?
119? //在Subject中注冊Observer,將兩者聯系在一起
120?
121? frame.registerObserver(frame2);
122?
123? …………..
124?
125? frame.setVisible(true);
126?
127? frame2.setVisible(true);
128?
129? ……………..
130?
131? }
132?
133? }
134?
135
我認為在JDK中這個Observer模式的實現,對于一般的Observer模式的應用,已經是非常的足夠了的。但是一方面它用一個類來實現了Subject,另一方面它使用Vector來保存Subject對于Observer的引用,這雖然簡化了編程的過程,但會限制它在一些需要更為靈活,復雜的設計中的應用,有時候(雖然這種情況不多),我們還不得不重新編寫新的Subject對象和額外的Manager對象來實現更為復雜的Observer模式的應用。
隨著現代軟件工業的不斷進步,軟件系統的規模的日益擴大,越來越需要對某些個不斷出現的問題進行模式化思維,以成功的經驗或者失敗的教訓來減少軟件開發失敗的風險。模式代表了一種文檔化的經驗,它為某一類的問題提供了最好(或者說很好)的解決方案,使得即使不是經驗豐富的軟件工程師,也能夠根據模式來構建相對成功的系統。本節給出的一個Obverser模式的示例,比較好的說明了這一點。Obverser模式主要解決在對象間的狀態映射或者鏡像的問題。
在設計一般用途的軟件的時候,在C或者C++語言中,用的很多的一個技巧就是回調函數(Callback),所謂的回調函數,意指先在系統的某個地方對函數進行注冊,讓系統知道這個函數的存在,然后在以后,當某個事件發生時,再調用這個函數對事件進行響應。在C或者C++中,實現的回調函數方法是使用函數指針。但是在Java中,并不支持指針,因而就有了Command模式,這一回調機制的面向對象版本。
Command模式用來封裝一個命令/請求,簡單的說,一個Command對象中包含了待執行的一個動作(語句)序列,以執行特定的任務。當然,并不是隨便怎么樣的語句序列都可以構成一個Command對象的,按照Command模式的設計,Command對象和它的調用者Incvoker之間應該具有接口約定的。也就是說,Invoker得到Command對象的引用,并調用其中定義好的方法,而當Command對象改變(或者是對象本身代碼改變,或者干脆完全另外的一個Command對象)之后,Invoker中的代碼可以不用更改。這樣,通過封裝請求,可以把任務和任務的實現加以分離。
圖二:Command模式的類圖
而對于請求的處理又有兩種不同的方法,一種是Command只充當代理,將請求轉發給某個接受者對象,還有一種是Command對象自己處理完所有的請求操作。當然,這只是兩個極端,更多的情況是Command完成一部分的工作,而另外的一部分這則交給接受者對象來處理。
在新的JDK的代理事件模型中,就可以看作是這樣的一個Command模式。在那個模型中,一個事件監聽者類EventListener監聽某個事件,并根據接口定義,實現特定的操作。比如,當用Document對象的addDocumentListener(DocumentListener listener) 方法注冊了一個DocumentListener后,以后如果在Document對象中發生文本插入的事件,DocumentListener中實現的insertUpdate(DocumentEvent e)方法就會被調用,如果發生文本刪除事件,removeUpdate(DocumentEvent e)方法就會被調用。怎么樣,想想看,這是不是一個Command模式的應用呢?
然而,最經典的Command模式的應用,莫過于Swing中的Action接口。Action實際上繼承的是ActionListener,也就是說,它也是一個事件監聽者(EventListener)。但是Action作為一種ActionListener的擴展機制,提供了更多的功能。它可以在其中包含對這個Action動作的一個或者多個文字的或圖標的描敘,它提供了Enable/Disable的功能許可性標志。并且,一個Action對象可以被多個Invoker,比如實現相同功能的按鈕,菜單,快捷方式所共享。而這些Invoker都知道如何加入一個Action,并充分利用它所提供的擴展機制。可以說,在這兒Action更像一個對象了,因為它不僅僅提供了對方法的實現,更提供了對方法的描敘和控制。可以方便的描敘任何的事務,這更是面向對象方法的威力所在。
下面我們看一個Command模式的應用的例子。假設要實現這樣的一個任務:Task Schedule。也就是說,我想對多個任務進行安排,比如掃描磁盤,我希望它每1個小時進行一次,而備份數據,我希望它半個小時進行一次,等等等等。但是,我并不希望作為TaskSchedule的類知道各個任務的細節內容,TaskSchedule應該只是知道Task本身,而對具體的實現任務的細節并不理會。因而在這兒,我們就需要對TaskSchedule和Task進行解耦,將任務和具體的實現分離出來,這不正是Command模式的用武之地嗎?
圖三:Command模式的應用例子
程序清單:
1?//抽象的Task接口,作為回調的Command模式的主體2?public?interface?Task {
3???public?void?taskPerform();
4?}
5?//具體的實現了Task接口的子類,實現特定的操作。
6?public?class?BackupTask?implements?Task{
7???public?void?taskPerform(){
8?????System.out.println("Backup Task has been performed");
9???}
10?}
11?//具體的實現了Task接口的子類,實現特定的操作。
12?public?class?ScanDiskTask?implements?Task{
13???public?void?taskPerform(){
14?????System.out.println("ScanDisk Task has been performed");
15???}
16?}
17?//一個封裝了Task的一個封裝類,提供了一些與Task相關的內容,也可以把這些內容
18?//這兒不過為了突出Command模式而把它單另出來,實際上可以和Task合并。
19?public?class?TaskEntry {
20???private?Task task;
21???private?long?timeInterval;
22???private?long?timeLastDone;
23???public?Task getTask() {
24?????return?task;
25???}
26???public?void?setTask(Task task) {
27?????this.task = task;
28???}
29???public?void?setTimeInterval(long?timeInterval) {
30?????this.timeInterval = timeInterval;
31???}
32???public?long?getTimeInterval() {
33?????return?timeInterval;
34???}
35???public?long?getTimeLastDone() {
36?????return?timeLastDone;
37???}
38???public?void?setTimeLastDone(long?timeLastDone) {
39?????this.timeLastDone = timeLastDone;
40???}
41???public?TaskEntry(Task task,long?timeInteral){
42?????this.task=task;
43?????this.timeInterval =timeInteral;
44???}
45?}
46?//調度管理Task的類,繼承Thread只是為了調用其sleep()方法,
47?//實際上,如果真的作Task調度的話,每個Task顯然應該用單獨的Thread來實現。
48?public?class?TaskSchedule?extends?java.lang.Thread {
49???private?java.util.Vector taskList=new?java.util.Vector();
50???private?long?sleeptime=10000000000l;//最短睡眠時間
51???public?void?addTask(TaskEntry taskEntry){
52?????taskList.add(taskEntry);
53?????taskEntry.setTimeLastDone(System.currentTimeMillis());
54?????if?(sleeptime>taskEntry.getTimeInterval())?
55?????sleeptime=taskEntry.getTimeInterval();
56???}
57???//執行任務調度
58???public?void?schedulePermorm(){
59?????try{
60???????sleep(sleeptime);
61???????Enumeration e = taskList.elements();
62???????while?(e.hasMoreElements()) {
63?????????TaskEntry te = (TaskEntry) e.nextElement();
64?????????if?(te.getTimeInterval() + te.getTimeLastDone() <?
65?????????????????System.currentTimeMillis()) {
66???????????te.getTask().taskPerform();
67???????????te.setTimeLastDone(System.currentTimeMillis());
68???????????}
69???????}
70?????}catch?(Exception e1){
71???????e1.printStackTrace();
72?????}
73???}
74???public?static?void?main (String args[]){
75?????TaskSchedule schedule=new?TaskSchedule();
76?????TaskEntry taks1=new?TaskEntry(new?ScanDiskTask(),10000);
77?????TaskEntry taks2=new?TaskEntry(new?BackupTask(),3000);
78?????schedule.addTask(taks1);
79?????schedule.addTask(taks2);
80?????while?(true){
81?????????schedule.schedulePermorm();
82???????}
83???}
84?}
85
程序本身其實沒有多大的意義,因而,程序在編碼的時候也只是用的最簡單的方法來實現的,如果要做一個真正的TaskSchedule的話,這個程序除了結構上的,其它沒有什么好值得參考的了。
基本上來說,AbstractFacotry模式和FactoryMethod模式所作的事情是一樣的,都是用來創建與具體程序代碼無關的對象,只是面對的對象層次不一樣,AbstractFactory創建一系列的對象組,這些對象彼此相關。而FactoryMethod往往只是創建單個的對象。
再開始這兩個模式之前,有必要先陳敘一個在設計模式,或者說在整個面向對象設計領域所遵循的一個設計原則:針對接口編程,而不是針對具體的實現。這個思想可以說是設計模式的基石之一。現在的很多對象模型,比如EJB,COM+等等,無不是遵照這個基本原則來設計的。針對接口編程的好處有很多,通過接口來定義對象的抽象功能,方便實現多態和繼承;通過接口來指定對象調用之間的契約,有助于協調對象之間的關系;通過接口來劃分對象的職責,有助于尋找對象,等等。
AbstractFactory和FactoryMethod,還有其他的一些創建型的設計模式,都是為了實現這個目的而設計出來的。它們創建一個個符合接口規范的對象/對象組,使得用同一個Factory創建出來的對象/對象組可以相互替換。這種可替換性就稱為多態,是面向對象的核心思想之一。而多態,是通過動態綁定來實現的。
圖四:AbstractFactory模式的類圖
客戶程序使用具體的AbstractFacotry對象(ConcreteFactoryX)調用CreateProductX()方法,生成具體的ConcreteProductX。每個AbstractFactory所能生成的對象,組成一個系列的對象組,他們可能是相互相關的,緊耦合的。應為各個AbstractFactory對象所能夠生成的對象組都遵循一組相同的接口(AbstractProductX),因而當程序是針對接口進行編程的時候,這些實現方法各不相同的對象組卻可以相互的替換。
實際上,客戶程序本身并不關心,也不知道具體使用的是那些產品對象。它甚至能夠不理會到底是哪個AbstractFactory對象被創建。在這種情況下,你可能會問,那么一個AbstractFactory又該如何生成呢?這時候,就該用該FactoryMethod模式了。
前面有說過,AbstractFactory著重于創建一系列相關的對象,而這些對象與具體的AbstractFactory相關。而FactoryMethod則著重于創建單個的對象,這個對象決定于一個參數或者一個外部的環境變量的值;或者,在一個抽象類中定義一個抽象的工廠方法(也成為虛擬構造器),然后再實現的子類中返回具體的產品對象。
FactoryMethod可以借助一個參數或者一個外部的標志來判斷該具體生成的哪一個子類的實例。比如對于不同的具體情況,需要有不同的AbstractFactory來生成相應的對象組。這時候,FactoryMethod通常作為一個AbstractFactory對象的靜態方法出現,使得其能夠在具體的對象被創建之前就能夠被調用。
在JAVA中,應用這兩個模式的地方實在太多,下面我們來看一個在JAXP中這兩個模式的應用。JAXP是用來處理XML文檔的一個API。我們都知道XML文件的一個特點就是其平臺無關,流通性能好。因而往往也需要處理他們的程序具有更好的平臺無關性。Java語言是一個比較好的平臺無關語言,可以作為一個選擇,但是對XML進行解析的解析器確有很多。有時候需要在不同的解析器之間進行切換,這時候,JAXP的良好設計就能夠體現出來了。它能夠允許在不同解析器之間竟進行切換的時候,不用更改程序的代碼。
我們就拿JAXP中的DOM解析器來作為例子,來例示AbstractFactory和FactoryMethod的用法。
圖五:DOM中工廠模式的應用
上圖中為了方便起見,只畫出了抽象類和接口,DocumentBuilderFactory和DocumentBuilder都是抽象類。
DocumentBuilderFactory的靜態方法newInstance()方法根據一個外部的環境變量javax.xml.parsers.DocumentBuilderFactory的值來確定具體生成DocumentBuilderFactory的哪一個子類。這兒的newInstance()是一個工廠方法。當DocumentBuilderFactory被創建后,可以調用其newDocumentBuilder()來創建具體一個DocumentBuilder的子類。然后再由DocumentBuilder來生成Document等DOM對象。
下面是創建一個DOM對象的代碼片段:
1?//第一步:創建一個DocumentBuilderFactory。2?????????DocumentBuilderFactory dbf = DocumentBuilderFactory.newInstance();
3?????????//第二步:創建一個DocumentBuilder
4?????????DocumentBuilder db = dbf.newDocumentBuilder();
5?????????//第三步:解析XML文件得到一個Document對象
6?????????Document doc = db.parse(new?File(filename));?
7?
8
在這兒,DocumentBuilder,Document,Node等等對象所組成的一個產品組,是和具體的DocumentBuilderFactory相關的。這也就是AbstractFactory模式的含義所在。
當然,FactoryMethod模式應用的很廣。這是一個具體的例子,但他不應該限制我們的思路,FactoryMethod和AbstractFactory是解決面向對象設計中一個基本原則--面向接口編程的主要方法。
Singleton模式
Singleton模式要解決的是對象的唯一性問題。由Singleton模式創建的對象在整個的應用程序的范圍內,只允許有一個對象的實例存在。這樣的情況在Java程序設計的過程中其實并不少見,比如處理JDBC請求的連接池(Connection Pool),再比如一個全局的注冊表(Register),等等,這都需要使用到Singleton,單件模式。
在Java中,最簡單的實現Singleton模式的方法是使用static修飾符,static可以用在內部類上,也可以用在方法和屬性上,當一個類需要被創建成Singleton時,可以把它所有的成員都定義成static,然后再用final和private來修飾其構造函數,使其不能夠被創建和重載。這在程序語法上保證了只會有一個對象實例被創建。比如java.util.Math就是這樣的一個類。
而Singleton模式所作的顯然要比上面介紹的解決方法要復雜一些,也更為安全一些。它基本的思路也還是使用static變量,但是它用一個類來封裝這個static變量,并攔截對象創建方法,保證只有一個對象實例被創建,這兒的關鍵在于使用一個private或者protected的構造函數,而且你必須提供這樣的一個構造函數,否則編譯器會自動的為你創建一個public的構造函數,這就達不到我們想要的目的了。
1?public?class?Singleton {2?
3? //保存唯一實例的static變量
4?
5? static?private?Singleton _instance =?null;
6?
7? /*為了防止對象被創建,可以為構造函數加上private修飾符,但是這同樣也防止了子類的對象被創建,因而,可以選用protected修飾符來替代private。*/
8?
9? protected?Singleton() {
10?
11? // ...
12?
13? }
14?
15? //static方法用來創建/訪問唯一的對象實例,這兒可以對對象的創建進行控制,使得可//以很容易的實現只允許指定個數的對象存在的泛化的Singleton模式。
16?
17? static?public?Singleton instance() {
18?
19? if(null?== _instance) {
20?
21? _instance =?new?Singleton();
22?
23? }
24?
25? return?_instance;
26?
27? }
28?
29? // ...
30?
31? }
32?
33
對象創建的方法,除了使用構造函數之外,還可以使用Object對象的clone()方法,因而在Singleton中也要注意這一點。如果Singleton類直接繼承于Object,因為繼承于Object的clone()方法仍保留有其protected修飾,因而不能夠被其他外部類所調用,所以可以不用管它,但是如果Singleton繼承于一個其他的類,而這個類又有重載clone()方法,這時就需要在Singleton中再重載clone()方法,并在其中拋出CloneNotSupportedException,這樣就可以避免多個Singleton的實例被創建了。
在JDK1.2以前的版本中使用Singleton模式的時候有一些需要額外注意的地方,因為Singleton類并沒有被任何其他的對象所引用,所以這個類在創建后一段時間會被unload,Singleton類的靜態方法就會出現問題,這是由于Java中垃圾收集機制造成的。解決的方法也很容易,只需要為其創建一個引用就行了。而在JDK1.2以后的版本中,Sun重新定義了Java規范,改正了其垃圾收集機制中的一些問題,這個問題也就不復存在了,這兒指出只是為了提起大家的主意。
Command模式用來封裝請求,也描敘了一致性的發送請求的接口,允許你配置客戶端以處理不同的請求,為程序增添了更大的靈活性。Singleton模式為提供對象的單一入口提供了幫助。AbstractFactory和FactoryMethod模式在功能上比較類似,都是用來處理對象的創建的,但應用在不同的層面上。在創建型模式中,還有Builder模式和Prototype模式,這兒不打算詳細的討論了,簡單的說,Builder模式用來處理對象創建的細節。在兩個工廠模式中都沒有涉及到對象創建的具體細節,都是通過接口來返回一個給定類型的對象。而Builder模式則需要對創建一個給定類型對象的過程進行建模。這對創建復雜對象時很有用,使得創建對象的算法獨立于對象各個組成部分的創建。而Prototype模式使用原型機制,通過創建簡單原型的拷貝來創建對象。
當初Java剛剛推出來的時候,AWT可是一個比較熱的話題,雖然現在有被Swing取代的趨勢。但是我一直都覺得AWT也有其優勢,至少它使用的本地代碼就要比Swing快上許多,而且,可以為用戶提供熟悉的本地操作系統界面。如果在Windows XP中運行基于AWT的程序的話,XP中絢爛多變的界面Theme可以輕易應用到AWT程序中,而Swing就不行了,因為AWT所調用的是本帶代碼,使用的是本地的窗體控件。當然,Swing也有其好處,不可一概而論。
簡單來講,AWT提供對程序員的是對窗體界面系統的抽象,而在內部實現中,針對每一種操作系統,分別有不同實現,這就是同位體(Peer)的概念。當程序員調用AWT對象時,調用被轉發到對象所對應的一個Peer上,在由Peer調用本地對象方法,完成對象的顯示。例如,如果你使用AWT創建了一個Menu類的實例,那么在程序運行時會創建一個菜單同位體的實例,而由創建的同位體的來實際執行菜單的現實和管理。不同的系統,有不同的同位體實現,Solaris JDK將產生一個Motif菜單的同位體,Windows下的JDK將產生一個Windows的菜單的同位體,等等。同位體的使用,使得交叉平臺窗口工具的開發變得極為迅速,因為同位體的使用可以避免重新實現本地窗口控件中已經包含的方法。
圖六:AWT中的組件和其對等體
實際上,從設計的角度來看,這是一個抽象和實現分離的過程--AWT是抽象,同位體是實現,抽象和實現各自成為一個對象體系,它們由一個橋連接起來,可以各自發展各自的對象層次,而不必顧慮另一方面。這就是Bridge模式所提供的思想。Bridge模式更可以提供在各個不同的實現中動態的進行切換,而不必從新編譯程序。
通常,Bridge模式和AbstractFactory模式一起工作,由AbstractFactory來創建一個具體實現的對象體系。特殊的,當只有一個實現的時候,可以將Implementor抽象類去掉。這樣,在抽象和實現之間建立起了一一對應的關系,但這并不損害Bridge模式的內涵。這被稱為退化了的Bridge模式。
很多時候,Abstraction層次和Implementor層次之間的方法都不是一一對應的,也就是說,在Abstraction和Implementor之不是簡單的的消息轉發。通常,我們會將Abstraction作為一個抽象類(而不是接口)來實現。在Implementor層次中定義底層的,或者稱之為原子方法,而在Abstraction層次中定義一些中高層的基于原子方法的抽象方法。這樣,就能更為清晰的劃分Abstraction和Implementor,類的結構也更為清晰。
圖七:Bridge模式對系統的劃分
下面,我們來看一個Bridge模式的具體應用。考慮這樣的一個問題,需要生成一份報告,但是報告的格式并沒有確定,可能是HTML文件,也可能是純ASCII文本。報告本身也可能分為很多種,財務報表,貨物報表,等等問題很簡單,用繼承也較容易實現,因為相互之間的組合關系并不是很多。但是,我們現在需要用Bridge的觀點來看問題。
在Bridge模式中,使用一個Report類來描敘一個報告的抽象,用一個Reporter類來描敘Report的實現,它的子類有HTMLReporter和ASCIIReporter,用來分別實現HTML格式和ASCII格式的報告。在Report層次下面,有具體的一個StockListReport子類,用來表示貨物清單報告。
??1? public?abstract?class?Report??2?
??3? {
??4?
??5? Reporter reporter;
??6?
??7? public?Report(Reporter reporter) {
??8?
??9? this.reporter = reporter;
10?
11? }
12?
13? //抽象類使用橋接對象的方法來實現一個任務
14?
15? public?void?addReportItem(Object item){
16?
17? reporter.addLine(item.toString());
18?
19? }
20?
21? public?void?addReportItems(List items){
22?
23? Iterator iterator = items.iterator();
24?
25? while?( iterator.hasNext() )
26?
27? {
28?
29? reporter.addLine(iterator.next().toString());
30?
31? }
32?
33? }
34?
35? public?String report(){
36?
37? return?reporter.getReport();
38?
39? }
40?
41? }
42?
43? public?class?StockListReport?extends?Report{
44?
45? ArrayList stock=new?ArrayList();
46?
47? public?StockListReport(Reporter reporter){
48?
49? super(reporter);
50?
51? }
52?
53? public?void?addStockItem(StockItem stockItem){
54?
55? stock.add(stockItem);
56?
57? addReportItem(stockItem);
58?
59? }
60?
61? }
62?
63? //實現層次的抽象父類定義原子方法,供抽象層次的類調用
64?
65? public?abstract?class?Reporter{
66?
67? String header = "";
68?
69? String trailer = "";
70?
71? String report = "";
72?
73? public?abstract?void?addLine(String line);
74?
75? public?void?setHeader(String header){
76?
77? this.header = header;
78?
79? }
80?
81? public?void?setTrailer(String trailer){
82?
83? this.trailer = trailer;
84?
85? }
86?
87? public?String getReport(){
88?
89? return?header+report+trailer;
90?
91? }
92?
93? }
94?
95? public?class?HTMLReporter?extends?Reporter{
96?
97? public?HTMLReporter(){
98?
99? setHeader("\n\n\n");
100?
101? setTrailer("\n");
102?
103? }
104?
105? public?void?addLine(String line){
106?
107? report += line + "
108?
109? \n";
110?
111? }
112?
113? }
114?
115? public?class?ASCIIReporter?extends?Reporter{
116?
117? public?void?addLine(String line) {
118?
119? report += line + "\n";
120?
121? }
122?
123? }
124?
125
實際上,Bridge模式是一個很強大的模式,可以應用在很多方面。其基本思想:分離抽象和實現,是設計模式的基礎之一。正如GOF所提到的:"找到變化的部分,并將其封裝起來";"更多的考慮用對象組合機制,而不是用對象繼承機制"。Bridge模式很好的體現了這幾點。
在使用Java中的IO類庫的時候,是不是快要被它那些功能相似,卻又絕對可稱得上龐雜的類搞得要發瘋了?或許你很不明白為什么要做這么多功能相似的幾十個類出來,這就是Decorator模式將要告訴你的了。
在IO處理中,Java將數據抽象為流(Stream)。在IO庫中,最基本的是InputStream和OutputStream兩個分別處理輸出和輸入的對象(為了敘述簡便起見,這兒只涉及字節流,字符流和其完全相似),但是在InputStream和OutputStream中之提供了最簡單的流處理方法,只能讀入/寫出字符,沒有緩沖處理,無法處理文件,等等。它們只是提供了最純粹的抽象,最簡單的功能。
如何來添加功能,以處理更為復雜的事情呢?你可能會想到用繼承。不錯,繼承確實可以解決問題,但是繼承也帶來更大的問題,它對每一個功能,都需要一個子類來實現。比如,我先實現了三個子類,分別用來處理文件,緩沖,和讀入/寫出數據,但是,如果我需要一個既能處理文件,又具有緩沖功能的類呢?這時候又必須在進行一次繼承,重寫代碼。實際上,僅僅這三種功能的組合,就已經是一個很大的數字,如果再加上其它的功能,組合起來的IO類庫,如果只用繼承來實現的話,恐怕你真的是要被它折磨瘋了。
圖八:JDK中IO流的類層次
Decorator模式可以解決這個問題。Decorator字面的意思是裝飾的意思,在原有的基礎上,每添加一個裝飾,就可以增加一種功能。這就是Decorator的本意。比如,對于上面的那個問題,只需要三個Decorator類,分別代表文件處理,緩沖和數據讀寫三個功能,在此基礎上所衍生的功能,都可以通過添加裝飾來完成,而不必需要繁雜的子類繼承了。更為重要的是,比較繼機制承而言,Decorator是動態的,可以在運行時添加或者去除附加的功能,因而也就具有比繼承機制更大的靈活性。
上面就是Decorator的基本思想,下面的是Decorator模式的靜態結構圖:
圖九:Decorator模式的類圖
可以看到,一個Decorator與裝飾的Subject對象有相同的接口,并且除了接口中給出的方法外,每個Decorator均有自己添加的方法,來添加對象功能。每個Decorator均有一個指向Subject對象的引用,附加的功能被添加在這個Subject對象上。而Decorator對象本身也是一個Subject對象,因而它也能夠被其他的Decorator所修飾,提供組合的功能。
在Java IO操作中,經常可以看到諸如如下的語句:
1?myStringBuffer=new?StringBuffer("This is a sample string to be read");2?
3? FilterInputStream myStream=new?LineNumberInputStream
4?
5? (?new?BufferInputStream(?new?StringBufferInputStream( myStringBuffer)));
6?
7? myStream.read();
8?
9? myStream.line();
10?
11
多個的Decorator被層疊在一起,最后得到一個功能強大的流。既能夠被緩沖,又能夠得到行數,這就是Decorator的威力!
不僅僅如此,Java中的IO還允許你引入自定義的Decorator,來實現自己想要的功能。在良好的設計背景下,這做起并不復雜,只需要4步:
創建兩個分別繼承了FilterInputStream和 FilterOutputStream的子類
重載read()和write()方法來實現自己想要的功能。
可以定義或者重載其它方法來提供附加功能。
確定這兩個類會被一起使用,因為它們在功能上是對稱的。
就這樣,你就可以無限的擴展IO的功能了。
在了解了IO中的Decorator后,我們再來看一個Decorator模式應用的具體的例子。這個例子原本是出現在GOF書中的,這兒稍作改動,引來示例。
在一個圖形用戶界面(GUI)中,一個組件有時候需要用到邊框或者滾動條,而有時候又不需要,有時候可能兩者都要用到。當需要動態的去處或者添加職能的時候,就可以考慮使用Decorator模式了。這兒對于一個VisualComponent組件對象,我們引入了兩個Decorator類:BoderDecorator和ScrollDecorator,分別用來為組件添加邊框和處理滾動。程序類圖如下:
圖十:Decorator模式的應用例子
程序寫得很簡單,沒有包括具體的代碼,只是有一個可以運行的框架以供參考。代碼如下:
??1? //Client類用來創建窗體和組件對象,這兒可以看到Decorator是如何組合和應用的??2?
??3? class?Client{
??4?
??5? public?static?void?main (String[] args ){
??6?
??7? Window window =?new?Window ();
??8?
??9? TextView textView =?new?TextView ();
10?
11? window.setContents (
12?
13? new?BorderDecorator (
14?
15? new?ScrollDecorator (textView, 500), 1));
16?
17? }
18?
19? }
20?
21? //Windows類用來容納組件對象
22?
23? class?Window{
24?
25? VisualComponent contents;
26?
27? public?Window () {}
28?
29? public?void?setContents (VisualComponent vc){
30?
31? contents = vc;
32?
33? }
34?
35? }
36?
37? //VisualComponent類定義了組件的接口
38?
39? class?VisualComponent{
40?
41? public?VisualComponent (){}
42?
43? public?void?draw (){}
44?
45? public?void?resize (){}
46?
47? }
48?
49? //TextView類是一個顯示文本的具體的組件
50?
51? class?TextView?extends?VisualComponent{
52?
53? public?TextView (){}
54?
55? public?void?draw (){
56?
57? …
58?
59? }
60?
61? public?void?resize (){
62?
63? …
64?
65? }
66?
67? }
68?
69? //Decorator類繼承于VisualComponent,定義所有Decorator的缺省方法實現
70?
71? class?Decorator?extends?VisualComponent{
72?
73? private?VisualComponent component;
74?
75? public?Decorator (VisualComponent vc) {
76?
77? this.component=vc;
78?
79? }
80?
81? public?void?draw () {
82?
83? component.draw ();
84?
85? }
86?
87? public?void?resize () {
88?
89? component.resize ();
90?
91? }
92?
93? }
94?
95? //BorderDecorator類為組件提供邊框
96?
97? class?BorderDecorator?extends?Decorator{
98?
99? private?int?width;
100?
101? public?BorderDecorator (VisualComponent vc,?int?borderWidth){
102?
103? super?(vc);
104?
105? width = borderWidth;
106?
107? }
108?
109? public?void?draw (){
110?
111? super.draw ();
112?
113? drawBorder (width);
114?
115? }
116?
117? private?void?drawBorder (int?width){
118?
119? …
120?
121? }
122?
123? }
124?
125? //ScrollDecorator類為組件提供滾動條
126?
127? class?ScrollDecorator?extends?Decorator{
128?
129? private?int?scrollSize;
130?
131? public?ScrollDecorator (VisualComponent vc,?int?scrSize){
132?
133? super?(vc);
134?
135? scrollSize = scrSize;
136?
137? }
138?
139? public?void?draw (){
140?
141? scroll();
142?
143? super.draw ();
144?
145? }
146?
147? private?void?scroll (){
148?
149? …
150?
151? }
152?
153? }
154
Decorator確實能夠很好的緩解當功能組合過多時子類繼承所能夠帶來的問題。但是在得到很大的靈活性的同時,Decorator在使用時也表現得較為復雜。看看僅僅為了得到一個IO流,除了要創建核心的流外,還要為其加上各種各樣的裝飾類,這使得代碼變得復雜而難懂。有幾個人一開始時沒有被Java的IO庫嚇一跳呢?
Bridge模式用來分離抽象和實現,使得這兩個部分能夠分別的演化而不必修改另外一部分的內容。通常的,可以在實現部分定義一些基本的原子方法,而在抽象部分則通過組合定義在實現層次中的原子方法來實現系統的功能。Decorator模式通過聚合機制來為對象動態的添加職責,解決了在子類繼承中容易引起的子類爆炸的問題。
毫無疑問的,AWT中的Component-Container體系就是一個很好的Composite模式的例子。Container繼承于Component,而Container中有可以包含有多個Component,因為Container實際上也是Component,因而Container也可以包含Container。這樣通過Component-Container結構的對象組合,形成一個樹狀的層次結構。這也就是Composite模式所要做的。
Composite模式是為了簡化編程而提出的,一般的在編程的時候,如果嚴格的區分Component和Container的話,有時候會帶來許多不便,而且這些往往是沒有必要的。比如,我要在一個Container中放置一個Component,我并不需要知道這個Component到底是一個Container,或者就是一個一般的Component,在父級容器中所要做的,只是記錄一個Component的引用,在需要的時候調用Component的繪制方法來顯示這個Component。當這個Component確實是一個Container的時候,它可以通過Container重載后的繪制方法,完成對這個容器的顯示,并把繪制消息傳遞給到它的子對象去。也就是說,對一個父級容器而言,它并不不關心,其子對象到底是一個Component,還是一個Container。它需要將Component和Container統一對待。
圖十一:Composite模式的類圖
Composite模式比較簡單,實現起來也不復雜,但是有一定的局限性。比如,在處理樹的時候,我們往往需要處理三類對象:子樹,頁節點和非頁節點。而在Composite模式中對于子樹和非葉節點的區分并不明顯,而是把他們合成為一個Composite對象了。而且在GOF給出的Composite的模式中,對于添加,刪除子節點等屬于Composite對象的的方法,是放在了Component對象中的,這雖然在實現的時候可以區分開來,但容易造成一些概念上的誤解。
由上所敘,我們可以提出一個改進了的Composite模式,引入子樹對象,從而將子樹和非葉節點分開,如下圖所示:
圖十二:Composite模式的一種變體
雖然將Composite從Component類層次中分離出來,但并沒有損害Composite模式的內涵。這樣做不一定就會比上面的那個要好,各有不同的應用,不過有時候用這樣的方法來處理子樹要容易些,概念上也更為清晰。
下面的代碼,給出了一個Composite模式簡單的Java實現:
1? public?abstract?class?Component{2?
3? public?abstract?void?operation();
4?
5? public?void?add(Component component){};
6?
7? public?void?remove(Component component){};
8?
9? }
10?
11? import?java.util.*;
12?
13? public?class?Composite?extends?Component{
14?
15? String name;
16?
17? ArrayList children =?new?ArrayList();
18?
19? public?Composite(String name){
20?
21? this.name = name;
22?
23? }
24?
25? public?void?add(Component component){
26?
27? children.add(component);
28?
29? }
30?
31? public?void?remove(Component component){
32?
33? children.remove(component);
34?
35? }
36?
37? public?void?operation(){
38?
39? System.out.println(name);
40?
41? Iterator iterator = children.iterator();
42?
43? while(iterator.hasNext()){
44?
45? Component child = (Component)iterator.next();
46?
47? child.operation();
48?
49? }
50?
51? }
52?
53? }
54?
55? public?class?Leaf?extends?Component{
56?
57? String name;
58?
59? public?Leaf(String name){
60?
61? this.name = name;
62?
63? }
64?
65? public?void?operation(){
66?
67? System.out.println(name);
68?
69? }
70?
71? }
72
Strategy模式主要用來將算法實現從類中分離出來,并封裝在一個單獨的類中。更簡單的說,對象與其行為(behaviour)這本來緊密聯系的兩部分被解耦,分別放在了兩個不同的類中。這使得對同一個行為,可以方便的在任何時候切換不同的實現算法。而通過對策略的封裝,為其提供統一的接口,也可以很容易的引入新的策略。
AWT的LayoutManager,是Strategy模式的一個例子。對于GUI而言,每個組件(Component)在容器中(Container)的排放是需要遵循一定的算法的。通常的方法是使用絕對坐標,就像VB,Delphi之類的工具所作的那樣,記錄每個組件在容器中的位置。這當然會帶來一些問題,比如在窗體縮放的時候,就需要手工編碼改變組件的大小和位置,以使得原來的比例得以保存。而在AWT中,引入了布局管理器(LayoutManager)的概念,使得布局的方法大大豐富,編碼過程也變得簡單。
一個容器,比如Applet,Panel等,僅僅記錄其包含的組件,而布局管理器中封裝了對容器中組件進行布局的算法,具體地說,就是指明容器中組件的位置和尺寸的大小。通過布局管理器,你只需要確定想放置的組件間的相對位置即可,這一方面簡化編碼,另一方面也有助于實現軟件的平臺無關性。
圖十三:AWT中的容器和布局管理器的關系
每一個容器均有一個布局管理器,當容器需要布置它的組件時,它調用布局管理器的方法布置容器內的組件。LayoutManager2繼承于LayoutManager,提供更為細致的布局功能,它可以讓布局管理器為組件加上約束條件已確定組件如何被布置。例如,為了確定組件被擺放在邊框內的位置,BorderLayout在它的組件上加上方向指示。
特別的,通過實現LayoutManager或者LayoutManager2接口,可以很容易實現自定義的布局策略。
回到模式的話題上來,如果有幾個很相似的類,其區別僅僅是在個別行為上的動作不同,這時候就可以考慮使用Strategy模式。這樣,通過策略組合,將原來的多個類精簡為一個帶有多個策略的類。這很符合OO設計的原則:找到變化的部分,并將其封裝起來!Strategy模式同樣的為子類繼承提供了一個好的替代方案,當使用繼承機制的時候,行為的改變是靜態的,你指能夠改變一次--而策略是動態的,可以在任何時候,切換任何次數。更為重要的是,策略對象可以在不同的環境中被不同的對象所共享。以布局管理器為例,雖然每一個容器只有一個布局管理器,但是一個布局管理器可以為多個容器工作。
圖十四:Strategy模式的類圖
Strategy模式也有一些缺點,比如,應用程序必須知道所有的策略對象,并從中選者其一。而且在策略對象被使用的時候,它和Context對象之間通常是緊耦合的,Context對象必須為策略對象提供與具體算法相關的數據或者其它的東西,而這些數據的傳遞可能并不能夠風裝載抽象地策略類中,因為并不是所有的算法都會需要這些數據的。另外,因為策略對象通常由應用程序所創建,Context對象并不能夠控制Strategy的生命期,而在概念上,這個策略應該從屬于Context對象,其生命期不應該超出Context的范圍對象。
通常的,Strategy很容易和Bridge模式相混淆。確實,他們有著很相近的結構,但是,他們卻是為解決不同的問題而設計的。Strategy模式注重于算法的封裝,而Bridge模式注重于分離抽象和實現,為一個抽象體系提供不同的實現。
Iterator模式用來規格化對某一數據結構的遍歷接口。
JDK中在Collection Framework中引入了Iterator接口,提供對一個Collection的遍歷。每一個Collection類中都定義有從Collection接口中繼承而來的iterator()方法,來得到一個Iterator對象,我們稱之為遍歷器,Iterator接口很簡單:
hasNext():用來判斷在遍歷器中是否還有下一個元素。
next():返回遍歷器中的下一個元素。
remove():在被遍歷的Collection類中刪除最后被返回的那個對象。
我們就以最為常用的Vector為例,看看在Collection Framework中,Iterator模式是如何被實現的。在此之前,我們需要先了解一些Vector和Collection Framework的結構。
Collection接口作為這個Framework的基礎,被所有其它的集合類所繼承或者實現。對Collection接口,有一個基本的實現是抽象類AbstractCollection,它實現了大部分與具體數據結構無關的操作。比如判斷一個對象是否存在于這個集合類中的contains()方法:
1? public?boolean?contains(Object o) {2?
3? Iterator e = iterator();
4?
5? if?(o==null) {
6?
7? while?(e.hasNext())
8?
9? if?(e.next()==null)
10?
11? return?true;
12?
13? }?else?{
14?
15? while?(e.hasNext())
16?
17? if?(o.equals(e.next()))
18?
19? return?true;
20?
21? }
22?
23? return?false;
24?
25? }
26?
27
而這其中調用的iterator()方法是一個抽象方法,有賴于具體的數據結構的實現。但是對于這個containers()方法而言,并不需要知道具體的Iterator實現,而只需要知道它所提供的接口,能夠完成某類任務既可,這就是抽象類中抽象方法的作用。其它的在AbstractCollection中實現的非抽象方法,大部分都是依賴于抽象方法iterator()方法所提供的Iterator接口來實現的。這種設計方法是引入抽象類的一個關鍵所在,值得仔細領悟。
List接口繼承Collection接口,提供對列表集合類的抽象;對應的AbstractList類繼承AbstractCollection,并實現了List接口,作為List的一個抽象基類。它對其中非抽象方法的實現,也大抵上與AbstractCollection相同,這兒不再贅敘。
而對應于Collection的Iterator,List有其自己的ListIterator,ListIterator繼承于Iterator,并添加了一些專用于List遍歷的方法:
boolean hasPrevious():判斷在列表中當前元素之前是否存在有元素。
Object previous():返回列表中當前元素之前的元素。
int nextIndex():
int previousIndex():
void set(Object o):
void add(Object o):
ListIterator針對List,提供了更為強勁的功能接口。在AbstractList中,實現了具體的iterator()方法和listIterator()方法,我們來看看這兩個方法是如何實現的:
1? public?Iterator iterator() {2?
3? return?new?Itr();?//Itr是一個內部類
4?
5? }
6?
7? private?class?Itr?implements?Iterator {
8?
9? int?cursor = 0;//Iterator的計數器,指示當前調用next()方法時會被返回的元素的位置
10?
11? int?lastRet = -1;//指示剛剛通過next()或者previous()方法被返回的元素的位置,-1
12?
13? //表示剛剛調用的是remove()方法刪除了一個元素。
14?
15? //modCount是定義在AbstractList中的字段,指示列表被修改的次數。Iterator用//這個值來檢查其包裝的列表是否被其他方法所非法修改。
16?
17? int?expectedModCount = modCount;
18?
19? public?boolean?hasNext() {
20?
21? return?cursor != size();
22?
23? }
24?
25? public?Object next() {
26?
27? try?{
28?
29? //get方法仍然是一個抽象方法,依賴于具體的子類實現
30?
31? Object next = get(cursor);
32?
33? //檢查列表是否被不正確的修改
34?
35? checkForComodification();
36?
37? lastRet = cursor++;
38?
39? return?next;
40?
41? }?catch(IndexOutOfBoundsException e) {
42?
43? checkForComodification();
44?
45? throw?new?NoSuchElementException();
46?
47? }
48?
49? }
50?
51? public?void?remove() {
52?
53? if?(lastRet == -1)
54?
55? throw?new?IllegalStateException();
56?
57? checkForComodification();
58?
59? try?{
60?
61? //同樣remove(int)也依賴于具體的子類實現
62?
63? AbstractList.this.remove(lastRet);
64?
65? if?(lastRet < cursor)
66?
67? cursor--;
68?
69? lastRet = -1;
70?
71? expectedModCount = modCount;
72?
73? }?catch(IndexOutOfBoundsException e) {
74?
75? throw?new?ConcurrentModificationException();
76?
77? }
78?
79? }
80?
81? final?void?checkForComodification() {
82?
83? if?(modCount != expectedModCount)
84?
85? throw?new?ConcurrentModificationException();
86?
87? }
88?
89? }
90?
91
這兒的設計技巧和上面一樣,都是使用抽象方法來實現一個具體的操作。抽象方法作為最后被實現的內容,依賴于具體的子類。抽象類看起來很像是一個介于接口和子類之間的一個東西。
從設計上來講,有人建議所有的類都應該定義成接口的形式,這當然有其道理,但多少有些極端。當你需要最大的靈活性的時候,應該使用接口,而抽象類卻能夠提供一些缺省的操作,最大限度的統一子類。抽象類在許多應用框架(Application Framework)中有著很重要的作用。例如,在一個框架中,可以用抽象類來實現一些缺省的服務比如消息處理等等。這些抽象類能夠讓你很容易并且自然的把自己的應用嵌入到框架中去。而對于依賴于每個應用具體實現的方法,可以通過定義抽象方法來引入到框架中。
其實在老版本的JDK中也有類似的概念,被稱為Enumeration。Iterator其實與Enmeration功能上很相似,只是多了刪除的功能。用Iterator不過是在名字上變得更為貼切一些。模式的另外一個很重要的功用,就是能夠形成一種交流的語言(或者說文化)。有時候,你說Enumeration大家都不明白,說Iterator就都明白了。
Composite,Strategy和Iterator。Composite是一個結構性的模式,用來協調整體和局部的關系,使之能夠被統一的安排在一個樹形的結構中,并簡化了編程。Strategy模式與Bridge模式在結構上很相似,但是與Bridge不同在于,它是一個行為模式,更側重于結構的語義以及算法的實現。它使得程序能夠在不同的算法之間自由方便的作出選擇,并能夠在運行時切換到其他的算法,很大程度上增加了程序的靈活性。Iterator模式提供統一的接口操作來實現對一個數據結構的遍歷,使得當數據結構的內部算法發生改變時,客戶代碼不需要任何的變化,只需要改變相應的Iterator實現,就可以無縫的集成在原來的程序中。
有了前面諸多設計模式的基礎,這兒可以提出一個比較特殊的模式MVC。MVC并不屬于GOF的23個設計模式之列,但是它在GOF的書中作為一個重要的例子被提出來,并給予了很高的評價。一般的來講,我們認為GOF的23個模式是一些中級的模式,在它下面還可以抽象出一些更為一般的低層的模式,在其上也可以通過組合來得到一些高級的模式。MVC就可以看作是一些模式進行組合之后的結果(實際上,MVC的出現要早于設計模式的提出,這而只是對它在設計模式的基礎上進行在分析)。如果沒有前面的基礎,理解MVC或許會有一些困難。
MVC模式
MVC模式比較的特別,它含義比較的廣,涉及的層面也不僅僅是設計這一塊,不好簡單的把它歸為設計模式。當然,它主要還是作為一個設計的概念被提到的,而且在Java體系中,MVC有著至關重要的作用。這兒提的是Java中的設計模式,當然不好拉了它不講了。
關于MVC的來龍去脈,這兒就不再講了。這里主s要講兩個方面的:作為設計模式的MVC和作為體系結構模式的MVC。
所謂MVC,指的是一種劃分系統功能的方法,它將一個系統劃分為三個部分:
模型(Model):封裝的是數據源和所有基于對這些數據的操作。在一個組件中,Model往往表示組件的狀態和操作狀態的方法。
視圖(View):封裝的是對數據源Model的一種顯示。一個模型可以由多個視圖,而一個視圖理論上也可以同不同的模型關聯起來。
控制器(Control):封裝的是外界作用于模型的操作。通常,這些操作會轉發到模型上,并調用模型中相應的一個或者多個方法。一般Controller在Model和View之間起到了溝通的作用,處理用戶在View上的輸入,并轉發給Model。這樣Model和View兩者之間可以做到松散耦合,甚至可以彼此不知道對方,而由Controller連接起這兩個部分。
有了前面介紹的諸多模式之后,就可以很容易的通過模式來解釋MVC的內在行為了。前面說過,在設計模式中,MVC實際上是一個比較高層的模式,它由多個更基本的設計模式組合而成,Model-View的關系實際上是Observer模式,模型的狀態和試圖的顯示相互響應,而View-Controller則是由Strategy模式所描敘的,View用一個特定的Controller的實例來實現一個特定的響應策略,更換不同的Controller,可以改變View對用戶輸入的響應。而其它的一些設計模式也很容易組合到這個體系中。比如,通過Composite模式,可以將多個View嵌套組合起來;通過FactoryMethod模式來指定View的Controller,等等。
使用MVC的好處,一方面,分離數據和其表示,使得添加或者刪除一個用戶視圖變得很容易,甚至可以在程序執行時動態的進行。Model和View能夠單獨的開發,增加了程序了可維護性,可擴展性,并使測試變得更為容易。另一方面,將控制邏輯和表現界面分離,允許程序能夠在運行時根據工作流,用戶習慣或者模型狀態來動態選擇不同的用戶界面。
Swing號稱是完全按照MVC的思路來進行設計的。在設計開始前,Swing的希望能夠達到的目標就包括:
模型驅動(Model-Driven)的編程方式。
提供一套單一的API,但是能夠支持多種視感(look-and-feel),為用戶提供不同的界面。
很自然的可以發現,使用MVC模式能夠有助于實現上面的這兩個目標。
嚴格的說,Swing中的MVC實際上是MVC的一個變體:M-VC。 Swing中只顯示的定義了Model接口,而在一個UI對象中集成了視圖和控制器的部分機制。View和Control比較松散的交叉組合在一起,而更多的控制邏輯是在事件監聽者部分引入的。
但是,這并沒有妨礙在Swing中體現MVC的精髓。事實上,在Swing的開發初期,Swing確實是按照標準的MVC模式來設計的,但是很快的問題就出現了:View和Controller實際上是緊密耦合的,很難作出一個能夠適應不同View的一般化的Controller來,而且,一般也沒有很大的必要。
在Swing中基本上每一個組件都會有對應的Model對象。但其并不是一一對應的,一個Model接口可以為多個Swing對向服務,例如:JProgressBar,JScrollBar,JSlider這三個組件使用的都是BoundedRangeModel接口。這種模型的共享更能夠從分的體現MVC的內涵。除了Model接口外,為了實現多個視感間的自由切換,每個Swing組件還包含一個UI接口--也就是View-Controller,負責對組件的繪制和接受用戶輸入。
Model-View是Subject和Obverser的關系,因而,模型的改變必須要在UI對象中體現出來。Swing使用了JavaBeans的事件模型來實現這種通知機制。具體而言,有兩種實現辦法,一是僅僅通知事件監聽者狀態改變了,然后由事件監聽者向模型提取必要的狀態信息。這種機制對于事件頻繁的組件很有效。另外的一種辦法是模型向監聽者發送包含了已改變的狀態信息的通知給UI。這兩種方法根據其優劣被分別是現在不同的組件中。比如在JScollBar中使用的是第一種方法,在JTable中使用的是第二種方法。而對Model而言,為了能夠支持多個View,它并不知道具體的每一個View。它維護一個對其數據感興趣的Obverser的列表,使得當數據改變的時候,能夠通知到每一個Swing組件對象。
上面講到的是作為設計模式的MVC。而在J2EE中,Sun更是將MVC提升到了一個體系結構模式的高度,這兒的MVC的含義就更為廣泛了。與Swing中不同的是,在這兒MVC的各個部件不再是單純的類或者接口,而是應用程序的一個組成部分!
在J2EE Blueprint中,Sun推薦了一種基于MVC的J2EE程序的模式。對于企業級的分布式應用程序而言,它更需要支持多種形式的用戶接口。比如,網上商店需要一個HTML的界面來同網上的客戶打交道,WML的界面可以提供給無線用戶,管理者可能需要傳統的基于Swing的應用程序來進行管理,而對對商業伙伴,基于XML的Web服務可能對他們更為方便。
MVC無疑是這樣一個問題的有效的解決方法,通過在控制和顯示邏輯分離出核心的數據存取功能,形成一個Model模塊,能夠讓多種視圖來共享這個Model。
在J2EE中有幾個核心的技術,JSP,JavaBean,Servlet,EJB,SessionBean,EntityBean構成了J2EE構架的基石。JSP能夠生成HTML,WML甚至XML,它對應于Web應用程序中的View部分。EJB作為數據庫與應用程序的中介,提供了對數據的封裝。一般EntityBean封裝的是數據,SessionBean是封裝的是對數據的操作。這兩個部分合起來,對應于Web應用程序的Model部分。在技術上,JSP能夠直接對EJB進行存取,但這并不是好辦法,那樣會混淆程序中的顯示邏輯和控制邏輯,使得JSP的重用性能降低。這時候有兩種解決方法,通過JavaBean或者Servlet作為中介的控制邏輯,對EJB所封裝的數據進行存取。這時,JavaBean或者Servlet對應于Web引用程序中的Controller部分。兩種類型的Controller各有其優缺點:JSP同Servlet的交互不容易規范化,使得交互的過程變得復雜,但是Servlet可以單獨同用戶交互,實際上JSP的運行時狀態就是Servlet;而由于JavaBean的規范性,JSP同JavaBean的交互很容易,利用JavaBean的get/set方法,JSP不需要過多的語句就可以完成數據的存取,這能夠讓JSP最大限度的集中在其視圖功能上,而且,在桌面應用程序中使用JavaBean也很容易,而用Servlet就相對麻煩許多。根據不同的問題背景,可以選取不同的Controller,有時候也可以兩者混合使用,或者直接在Servlet中調用JavaBean。
在J2EE中,MVC是一個大的框架,這時我們往往把它不再看作為設計模式,而是作為體系結構模式的一個應用了。
總結
在這篇文章中,從設計的角度,對Java的類庫進行了一些分析,并著重于設計模式在其中的使用問題。相信大家看了之后,不論對Java類庫本身,還是設計模式,都應該有了一個更深層次的了解。當然,Java類庫是一個非常龐大的東西,還有著許多設計精良的結構。因而,對Java源代碼的研究,不論對于編碼還是設計,都是很有裨益的。本人接觸設計模式的時間并不很長,對其的理解可能會有一些偏差,如有謬誤的地方,還請能夠提出,大家能夠共同的探討。
需要說明的是,對模式的描敘實際上是有一套完整的規格(或者語言)來進行的,涉及到模式的意圖(Intent),問題描敘(Problem),背景(Context),約束(Force),解決方案(Solution),結果(Resulting Context)等等。但這兒為了敘述的方便,并沒有將它們一一列舉。如果需要對模式有詳細系統的研究,就應該對這些規格敘述有更深入的了解。
總結
以上是生活随笔為你收集整理的从Java类库看设计模式的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 求一个大妈微信网名
- 下一篇: 求一个好听的古装男子名字