從Java類(lèi)庫(kù)看設(shè)計(jì)模式
2010-10-21 來(lái)源:IBM
很多時(shí)候,對(duì)于一個(gè)設(shè)計(jì)來(lái)說(shuō)(軟件上的,建筑上的,或者它他工業(yè)上的),經(jīng)驗(yàn)是至關(guān)重要的。好的經(jīng)驗(yàn)給我們以指導(dǎo),并節(jié)約我們的時(shí)間;壞的經(jīng)驗(yàn)則給我們以借鑒,可以減少失敗的風(fēng)險(xiǎn)。然而,從知識(shí)層面上來(lái)講,經(jīng)驗(yàn)只是作為一種工作的積累而存在于個(gè)人的大腦中的,很難被傳授或者記錄。為了解決這樣的問(wèn)題,人們提出了所謂的模式的概念。所謂模式,是指在一個(gè)特定背景下,反復(fù)出現(xiàn)的問(wèn)題解決方案。模式是經(jīng)驗(yàn)的文檔化。
軟件模式的概念現(xiàn)在比較的廣泛,涉及到分析,設(shè)計(jì),體系結(jié)構(gòu),編碼,測(cè)試,重構(gòu)等軟件構(gòu)造生命期中的各個(gè)部分。這兒主要討論的是設(shè)計(jì)模式,指的是在軟件設(shè)計(jì)過(guò)程中反復(fù)出現(xiàn)的一些問(wèn)題的解決方法了。不過(guò)我們一般在提到設(shè)計(jì)模式的時(shí)候,一般都是指GOF的經(jīng)典書(shū)《Design Pattern--Elements of Reusable Object-Oriented Software》出現(xiàn)的23個(gè)模式,因而,它是具體的針對(duì)于面向?qū)ο筌浖O(shè)計(jì)過(guò)程的。
從全局上看來(lái),模式代表了一種語(yǔ)言,一種被文檔化的經(jīng)驗(yàn),甚至是一種文化。往往很多不方便描敘,或者描敘起來(lái)很復(fù)雜的問(wèn)題,用模式語(yǔ)言來(lái)敘說(shuō),會(huì)讓聽(tīng)者產(chǎn)生心領(lǐng)神會(huì)的感覺(jué)。當(dāng)然,這需要交流雙方都能夠很好地把握模式語(yǔ)言的含義。然而,這并不是一件容易的事情。模式在各個(gè)人的理解上往往存在差異,這篇文章旨在從一個(gè)具體的應(yīng)用角度:Java類(lèi)庫(kù),來(lái)闡敘設(shè)計(jì)模式。并結(jié)合具體的例子,希望能夠加深大家對(duì)設(shè)計(jì)模式的理解。
這兒說(shuō)的Java類(lèi)庫(kù),其實(shí)并沒(méi)有局限于JDK本身,還包括了一些其他的類(lèi)庫(kù)中的例子,比如JAXP等(當(dāng)然,下一個(gè)版本的JDK中也會(huì)包含JAXP了)。其實(shí)設(shè)計(jì)模式的思想現(xiàn)在應(yīng)用的如此廣泛,無(wú)論在什么樣的設(shè)計(jì)中,只要稍微大一點(diǎn)的設(shè)計(jì),都可以找到很多很多設(shè)計(jì)模式的蹤跡,或者說(shuō)都不可避免的用到設(shè)計(jì)模式。下面所講的設(shè)計(jì)模式,大部分都是GOF的那部經(jīng)典中出現(xiàn)過(guò)的23個(gè)模式,然而,還有一些,比如MVC,并不屬于那里。一般的來(lái)講,我們認(rèn)為GOF的23個(gè)模式是一些中級(jí)的模式,在它下面還可以抽象出一些更為一般的低層的模式,在其上也可以通過(guò)組合來(lái)得到一些高級(jí)的模式。當(dāng)然,這兒的低中高的區(qū)別,如同區(qū)別不同的語(yǔ)言一樣,并沒(méi)有優(yōu)劣之分,僅僅是在應(yīng)用層面上的區(qū)別。
Observer模式
Observer模式的功用,是希望兩個(gè)(或多個(gè))對(duì)象,我們稱(chēng)之為Subject和Observer,當(dāng)一方的狀態(tài)發(fā)生改變的時(shí)候,另一方能夠得到通知。也就是說(shuō),作為Observer的一方,能夠監(jiān)視到Subject的某個(gè)特定的狀態(tài)變化,并為之做出反應(yīng)。一個(gè)簡(jiǎn)單的例子就是:當(dāng)一個(gè)用戶視圖中的數(shù)據(jù)被用戶改變后,后端的數(shù)據(jù)庫(kù)能夠得到更新,而當(dāng)數(shù)據(jù)庫(kù)被其他方式更新后,用戶視圖中的數(shù)據(jù)顯示也會(huì)隨之改變。
圖一:Obverser模式的類(lèi)圖
在JDK中實(shí)際上有一個(gè)對(duì)Observer模式的簡(jiǎn)單的實(shí)現(xiàn):就是類(lèi)java.util.Observerable和接口java.util.Observer。java.util.Observerable類(lèi)對(duì)應(yīng)于Subject,而java.util.Observer就是觀察者了。JDK中并沒(méi)有把這兩個(gè)部分都設(shè)計(jì)為接口,而是讓類(lèi)java.util.Observerable提供了部分的實(shí)現(xiàn),簡(jiǎn)化了許多編程的工作。當(dāng)然,這也減少了一定的靈活性。
下面列出了Observer和Observeral的函數(shù)列表,及其簡(jiǎn)單的功能說(shuō)明
java.util.Observer:
public void update(Observable obs, Object obj)
java.util.Observer 接口很簡(jiǎn)單,只定義了這一個(gè)方法,狹義的按照Observer模式的說(shuō)法,Observer應(yīng)該在這個(gè)方法中調(diào)用Subject的getXXX()方法來(lái)取得最新的狀態(tài),而實(shí)際上,你可以只是在其中對(duì)Subject的某些事件進(jìn)行響應(yīng)。這便是Java中的代理事件模型的一個(gè)雛形--對(duì)事件進(jìn)行響應(yīng)。只不過(guò),在Observer模式中將事件特定化為某個(gè)狀態(tài)/數(shù)據(jù)的改變了。
java.util.Observable
public void addObserver(Observer obs)
向Subject注冊(cè)一個(gè)Observer。也就是把這個(gè)Observer對(duì)象添加到了一個(gè)java.util.Observable內(nèi)部的列表中。在JDK中對(duì)于這個(gè)列表是簡(jiǎn)單的通過(guò)一個(gè)java.util.Vector類(lèi)來(lái)實(shí)現(xiàn)的,而實(shí)際上,在一些復(fù)雜的Observer模式的應(yīng)用中,需要把這個(gè)部分單另出來(lái)形成一個(gè)Manager類(lèi),來(lái)管理Subject和Observer之間的映射。這樣,Subject和Observer進(jìn)一步的被解藕,程序也會(huì)具有更大的靈活性。
public void deleteObserver(Observer obs)
從Subject中刪除一個(gè)已注冊(cè)了Observer的引用。
public void deleteObservers()
從Subjec中刪除所有注冊(cè)的Observer的引用。
public int countObservers()
返回注冊(cè)在Subject中的Observer個(gè)數(shù)。
protected void setChanged()
設(shè)置一個(gè)內(nèi)部的標(biāo)志以指明這個(gè)Ovserver的狀態(tài)已經(jīng)發(fā)生改變。注意這是一個(gè)protected方法,也就是說(shuō)只能在Observer類(lèi)和其子類(lèi)中被調(diào)用,而在其它的類(lèi)中是看不到這個(gè)方法的。
protected void clearChanged()
清除上敘的內(nèi)部標(biāo)志。它在notifyObservers()方法內(nèi)部被自動(dòng)的調(diào)用,以指明Subject的狀態(tài)的改變已經(jīng)傳遞到Ovserver中了。
public boolean hasChanged()
確定Subject的狀態(tài)是否發(fā)生了改變。
public void notifyObservers(Object obj)
它首先檢查那個(gè)內(nèi)部的標(biāo)志,以判斷狀態(tài)是否改變,如果是的話,它會(huì)調(diào)用注冊(cè)在Subject中的每個(gè)Observer的update()方法。在JDK中這個(gè)方法內(nèi)部是作為synchronized來(lái)實(shí)現(xiàn)的,也就是如果發(fā)生多個(gè)線程同時(shí)爭(zhēng)用一個(gè)java.util.Observerable的notifyObservers()方法的話,他們必須按調(diào)度的等待著順序執(zhí)行。在某些特殊的情況下,這會(huì)有一些潛在的問(wèn)題:可能在等待的過(guò)程中,一個(gè)剛剛被加入的Observer會(huì)被遺漏沒(méi)有被通知到,而一個(gè)剛剛被刪除了的Observer會(huì)仍然收到它已經(jīng)不想要了的通知。
public void notifyObservers()
等價(jià)于調(diào)用了notifyObservers(null)。
因而在Java中應(yīng)用Observer就很簡(jiǎn)單了,需要做的是:讓需要被觀察的Subject對(duì)象繼承java.util.Observerable,讓需要觀察的對(duì)象實(shí)現(xiàn)java.util.Observer接口,然后用java.util.Observerable的addObserver(Observer obj)方法把Observer注冊(cè)到Subject對(duì)象中。這已經(jīng)完成了大部分的工作了。然后調(diào)用java.util.Observerable的notifyObservers(Object arg)等方法,就可以實(shí)現(xiàn)Observer模式的機(jī)理。我們來(lái)看一個(gè)簡(jiǎn)單使用了這個(gè)模式的例子。這個(gè)例子有三個(gè)類(lèi):FrameSubject,DateSubject,F(xiàn)rameObject和EntryClass,F(xiàn)rameSubject中用戶可以設(shè)置被觀察的值,然后自動(dòng)的會(huì)在FrameObject中顯示出來(lái),DateSubject封裝被觀察的值,并且充當(dāng)Observer模式中的Subject。
1 public class FrameSubject extends JFrame {
2
3 …………..
4
5 //因?yàn)闊o(wú)法使用多重繼承,這兒就只能使用對(duì)象組合的方式來(lái)引入一個(gè)
6
7 //java.util.Observerable對(duì)象了。
8
9 DateSubject subject=new DateSubject();
10
11 //這個(gè)方法轉(zhuǎn)發(fā)添加Observer消息到DateSubject。
12
13 public void registerObserver(java.util.Observer o){
14
15 subject.addObserver(o);
16
17 }
18
19 //數(shù)據(jù)改變,事件被觸發(fā)后調(diào)用notifyObservers()來(lái)通知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 //封裝被觀察的數(shù)據(jù)
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 //數(shù)據(jù)改變后,setChanged()必須被調(diào)用,否則notifyObservers()方法會(huì)不起作用
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 //觀察的數(shù)據(jù)
80
81 int widthInfo=0;
82
83 int heightInfo=0;
84
85 //在update()方法中實(shí)現(xiàn)對(duì)數(shù)據(jù)的更新和其它必要的反應(yīng)。
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中注冊(cè)O(shè)bserver,將兩者聯(lián)系在一起
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
我認(rèn)為在JDK中這個(gè)Observer模式的實(shí)現(xiàn),對(duì)于一般的Observer模式的應(yīng)用,已經(jīng)是非常的足夠了的。但是一方面它用一個(gè)類(lèi)來(lái)實(shí)現(xiàn)了Subject,另一方面它使用Vector來(lái)保存Subject對(duì)于Observer的引用,這雖然簡(jiǎn)化了編程的過(guò)程,但會(huì)限制它在一些需要更為靈活,復(fù)雜的設(shè)計(jì)中的應(yīng)用,有時(shí)候(雖然這種情況不多),我們還不得不重新編寫(xiě)新的Subject對(duì)象和額外的Manager對(duì)象來(lái)實(shí)現(xiàn)更為復(fù)雜的Observer模式的應(yīng)用。
隨著現(xiàn)代軟件工業(yè)的不斷進(jìn)步,軟件系統(tǒng)的規(guī)模的日益擴(kuò)大,越來(lái)越需要對(duì)某些個(gè)不斷出現(xiàn)的問(wèn)題進(jìn)行模式化思維,以成功的經(jīng)驗(yàn)或者失敗的教訓(xùn)來(lái)減少軟件開(kāi)發(fā)失敗的風(fēng)險(xiǎn)。模式代表了一種文檔化的經(jīng)驗(yàn),它為某一類(lèi)的問(wèn)題提供了最好(或者說(shuō)很好)的解決方案,使得即使不是經(jīng)驗(yàn)豐富的軟件工程師,也能夠根據(jù)模式來(lái)構(gòu)建相對(duì)成功的系統(tǒng)。本節(jié)給出的一個(gè)Obverser模式的示例,比較好的說(shuō)明了這一點(diǎn)。Obverser模式主要解決在對(duì)象間的狀態(tài)映射或者鏡像的問(wèn)題。
在設(shè)計(jì)一般用途的軟件的時(shí)候,在C或者C++語(yǔ)言中,用的很多的一個(gè)技巧就是回調(diào)函數(shù)(Callback),所謂的回調(diào)函數(shù),意指先在系統(tǒng)的某個(gè)地方對(duì)函數(shù)進(jìn)行注冊(cè),讓系統(tǒng)知道這個(gè)函數(shù)的存在,然后在以后,當(dāng)某個(gè)事件發(fā)生時(shí),再調(diào)用這個(gè)函數(shù)對(duì)事件進(jìn)行響應(yīng)。在C或者C++中,實(shí)現(xiàn)的回調(diào)函數(shù)方法是使用函數(shù)指針。但是在Java中,并不支持指針,因而就有了Command模式,這一回調(diào)機(jī)制的面向?qū)ο蟀姹尽?div style="height:15px;">
Command模式用來(lái)封裝一個(gè)命令/請(qǐng)求,簡(jiǎn)單的說(shuō),一個(gè)Command對(duì)象中包含了待執(zhí)行的一個(gè)動(dòng)作(語(yǔ)句)序列,以執(zhí)行特定的任務(wù)。當(dāng)然,并不是隨便怎么樣的語(yǔ)句序列都可以構(gòu)成一個(gè)Command對(duì)象的,按照Command模式的設(shè)計(jì),Command對(duì)象和它的調(diào)用者Incvoker之間應(yīng)該具有接口約定的。也就是說(shuō),Invoker得到Command對(duì)象的引用,并調(diào)用其中定義好的方法,而當(dāng)Command對(duì)象改變(或者是對(duì)象本身代碼改變,或者干脆完全另外的一個(gè)Command對(duì)象)之后,Invoker中的代碼可以不用更改。這樣,通過(guò)封裝請(qǐng)求,可以把任務(wù)和任務(wù)的實(shí)現(xiàn)加以分離。
圖二:Command模式的類(lèi)圖
而對(duì)于請(qǐng)求的處理又有兩種不同的方法,一種是Command只充當(dāng)代理,將請(qǐng)求轉(zhuǎn)發(fā)給某個(gè)接受者對(duì)象,還有一種是Command對(duì)象自己處理完所有的請(qǐng)求操作。當(dāng)然,這只是兩個(gè)極端,更多的情況是Command完成一部分的工作,而另外的一部分這則交給接受者對(duì)象來(lái)處理。
在新的JDK的代理事件模型中,就可以看作是這樣的一個(gè)Command模式。在那個(gè)模型中,一個(gè)事件監(jiān)聽(tīng)者類(lèi)EventListener監(jiān)聽(tīng)某個(gè)事件,并根據(jù)接口定義,實(shí)現(xiàn)特定的操作。比如,當(dāng)用Document對(duì)象的addDocumentListener(DocumentListener listener) 方法注冊(cè)了一個(gè)DocumentListener后,以后如果在Document對(duì)象中發(fā)生文本插入的事件,DocumentListener中實(shí)現(xiàn)的insertUpdate(DocumentEvent e)方法就會(huì)被調(diào)用,如果發(fā)生文本刪除事件,removeUpdate(DocumentEvent e)方法就會(huì)被調(diào)用。怎么樣,想想看,這是不是一個(gè)Command模式的應(yīng)用呢?
然而,最經(jīng)典的Command模式的應(yīng)用,莫過(guò)于Swing中的Action接口。Action實(shí)際上繼承的是ActionListener,也就是說(shuō),它也是一個(gè)事件監(jiān)聽(tīng)者(EventListener)。但是Action作為一種ActionListener的擴(kuò)展機(jī)制,提供了更多的功能。它可以在其中包含對(duì)這個(gè)Action動(dòng)作的一個(gè)或者多個(gè)文字的或圖標(biāo)的描敘,它提供了Enable/Disable的功能許可性標(biāo)志。并且,一個(gè)Action對(duì)象可以被多個(gè)Invoker,比如實(shí)現(xiàn)相同功能的按鈕,菜單,快捷方式所共享。而這些Invoker都知道如何加入一個(gè)Action,并充分利用它所提供的擴(kuò)展機(jī)制。可以說(shuō),在這兒Action更像一個(gè)對(duì)象了,因?yàn)樗粌H僅提供了對(duì)方法的實(shí)現(xiàn),更提供了對(duì)方法的描敘和控制??梢苑奖愕拿钄⑷魏蔚氖聞?wù),這更是面向?qū)ο蠓椒ǖ耐λ凇?div style="height:15px;">
下面我們看一個(gè)Command模式的應(yīng)用的例子。假設(shè)要實(shí)現(xiàn)這樣的一個(gè)任務(wù):Task Schedule。也就是說(shuō),我想對(duì)多個(gè)任務(wù)進(jìn)行安排,比如掃描磁盤(pán),我希望它每1個(gè)小時(shí)進(jìn)行一次,而備份數(shù)據(jù),我希望它半個(gè)小時(shí)進(jìn)行一次,等等等等。但是,我并不希望作為T(mén)askSchedule的類(lèi)知道各個(gè)任務(wù)的細(xì)節(jié)內(nèi)容,TaskSchedule應(yīng)該只是知道Task本身,而對(duì)具體的實(shí)現(xiàn)任務(wù)的細(xì)節(jié)并不理會(huì)。因而在這兒,我們就需要對(duì)TaskSchedule和Task進(jìn)行解耦,將任務(wù)和具體的實(shí)現(xiàn)分離出來(lái),這不正是Command模式的用武之地嗎?
圖三:Command模式的應(yīng)用例子
程序清單:
1 //抽象的Task接口,作為回調(diào)的Command模式的主體
2 public interface Task {
3 public void taskPerform();
4 }
5 //具體的實(shí)現(xiàn)了Task接口的子類(lèi),實(shí)現(xiàn)特定的操作。
6 public class BackupTask implements Task{
7 public void taskPerform(){
8 System.out.println("Backup Task has been performed");
9 }
10 }
11 //具體的實(shí)現(xiàn)了Task接口的子類(lèi),實(shí)現(xiàn)特定的操作。
12 public class ScanDiskTask implements Task{
13 public void taskPerform(){
14 System.out.println("ScanDisk Task has been performed");
15 }
16 }
17 //一個(gè)封裝了Task的一個(gè)封裝類(lèi),提供了一些與Task相關(guān)的內(nèi)容,也可以把這些內(nèi)容
18 //這兒不過(guò)為了突出Command模式而把它單另出來(lái),實(shí)際上可以和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 //調(diào)度管理Task的類(lèi),繼承Thread只是為了調(diào)用其sleep()方法,
47 //實(shí)際上,如果真的作Task調(diào)度的話,每個(gè)Task顯然應(yīng)該用單獨(dú)的Thread來(lái)實(shí)現(xiàn)。
48 public class TaskSchedule extends java.lang.Thread {
49 private java.util.Vector taskList=new java.util.Vector();
50 private long sleeptime=10000000000l;//最短睡眠時(shí)間
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 //執(zhí)行任務(wù)調(diào)度
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
程序本身其實(shí)沒(méi)有多大的意義,因而,程序在編碼的時(shí)候也只是用的最簡(jiǎn)單的方法來(lái)實(shí)現(xiàn)的,如果要做一個(gè)真正的TaskSchedule的話,這個(gè)程序除了結(jié)構(gòu)上的,其它沒(méi)有什么好值得參考的了。
基本上來(lái)說(shuō),AbstractFacotry模式和FactoryMethod模式所作的事情是一樣的,都是用來(lái)創(chuàng)建與具體程序代碼無(wú)關(guān)的對(duì)象,只是面對(duì)的對(duì)象層次不一樣,AbstractFactory創(chuàng)建一系列的對(duì)象組,這些對(duì)象彼此相關(guān)。而FactoryMethod往往只是創(chuàng)建單個(gè)的對(duì)象。
再開(kāi)始這兩個(gè)模式之前,有必要先陳敘一個(gè)在設(shè)計(jì)模式,或者說(shuō)在整個(gè)面向?qū)ο笤O(shè)計(jì)領(lǐng)域所遵循的一個(gè)設(shè)計(jì)原則:針對(duì)接口編程,而不是針對(duì)具體的實(shí)現(xiàn)。這個(gè)思想可以說(shuō)是設(shè)計(jì)模式的基石之一?,F(xiàn)在的很多對(duì)象模型,比如EJB,COM+等等,無(wú)不是遵照這個(gè)基本原則來(lái)設(shè)計(jì)的。針對(duì)接口編程的好處有很多,通過(guò)接口來(lái)定義對(duì)象的抽象功能,方便實(shí)現(xiàn)多態(tài)和繼承;通過(guò)接口來(lái)指定對(duì)象調(diào)用之間的契約,有助于協(xié)調(diào)對(duì)象之間的關(guān)系;通過(guò)接口來(lái)劃分對(duì)象的職責(zé),有助于尋找對(duì)象,等等。
AbstractFactory和FactoryMethod,還有其他的一些創(chuàng)建型的設(shè)計(jì)模式,都是為了實(shí)現(xiàn)這個(gè)目的而設(shè)計(jì)出來(lái)的。它們創(chuàng)建一個(gè)個(gè)符合接口規(guī)范的對(duì)象/對(duì)象組,使得用同一個(gè)Factory創(chuàng)建出來(lái)的對(duì)象/對(duì)象組可以相互替換。這種可替換性就稱(chēng)為多態(tài),是面向?qū)ο蟮暮诵乃枷胫?。而多態(tài),是通過(guò)動(dòng)態(tài)綁定來(lái)實(shí)現(xiàn)的。
圖四:AbstractFactory模式的類(lèi)圖
客戶程序使用具體的AbstractFacotry對(duì)象(ConcreteFactoryX)調(diào)用CreateProductX()方法,生成具體的ConcreteProductX。每個(gè)AbstractFactory所能生成的對(duì)象,組成一個(gè)系列的對(duì)象組,他們可能是相互相關(guān)的,緊耦合的。應(yīng)為各個(gè)AbstractFactory對(duì)象所能夠生成的對(duì)象組都遵循一組相同的接口(AbstractProductX),因而當(dāng)程序是針對(duì)接口進(jìn)行編程的時(shí)候,這些實(shí)現(xiàn)方法各不相同的對(duì)象組卻可以相互的替換。
實(shí)際上,客戶程序本身并不關(guān)心,也不知道具體使用的是那些產(chǎn)品對(duì)象。它甚至能夠不理會(huì)到底是哪個(gè)AbstractFactory對(duì)象被創(chuàng)建。在這種情況下,你可能會(huì)問(wèn),那么一個(gè)AbstractFactory又該如何生成呢?這時(shí)候,就該用該FactoryMethod模式了。
前面有說(shuō)過(guò),AbstractFactory著重于創(chuàng)建一系列相關(guān)的對(duì)象,而這些對(duì)象與具體的AbstractFactory相關(guān)。而FactoryMethod則著重于創(chuàng)建單個(gè)的對(duì)象,這個(gè)對(duì)象決定于一個(gè)參數(shù)或者一個(gè)外部的環(huán)境變量的值;或者,在一個(gè)抽象類(lèi)中定義一個(gè)抽象的工廠方法(也成為虛擬構(gòu)造器),然后再實(shí)現(xiàn)的子類(lèi)中返回具體的產(chǎn)品對(duì)象。
FactoryMethod可以借助一個(gè)參數(shù)或者一個(gè)外部的標(biāo)志來(lái)判斷該具體生成的哪一個(gè)子類(lèi)的實(shí)例。比如對(duì)于不同的具體情況,需要有不同的AbstractFactory來(lái)生成相應(yīng)的對(duì)象組。這時(shí)候,F(xiàn)actoryMethod通常作為一個(gè)AbstractFactory對(duì)象的靜態(tài)方法出現(xiàn),使得其能夠在具體的對(duì)象被創(chuàng)建之前就能夠被調(diào)用。
在JAVA中,應(yīng)用這兩個(gè)模式的地方實(shí)在太多,下面我們來(lái)看一個(gè)在JAXP中這兩個(gè)模式的應(yīng)用。JAXP是用來(lái)處理XML文檔的一個(gè)API。我們都知道XML文件的一個(gè)特點(diǎn)就是其平臺(tái)無(wú)關(guān),流通性能好。因而往往也需要處理他們的程序具有更好的平臺(tái)無(wú)關(guān)性。Java語(yǔ)言是一個(gè)比較好的平臺(tái)無(wú)關(guān)語(yǔ)言,可以作為一個(gè)選擇,但是對(duì)XML進(jìn)行解析的解析器確有很多。有時(shí)候需要在不同的解析器之間進(jìn)行切換,這時(shí)候,JAXP的良好設(shè)計(jì)就能夠體現(xiàn)出來(lái)了。它能夠允許在不同解析器之間竟進(jìn)行切換的時(shí)候,不用更改程序的代碼。
我們就拿JAXP中的DOM解析器來(lái)作為例子,來(lái)例示AbstractFactory和FactoryMethod的用法。
圖五:DOM中工廠模式的應(yīng)用
上圖中為了方便起見(jiàn),只畫(huà)出了抽象類(lèi)和接口,DocumentBuilderFactory和DocumentBuilder都是抽象類(lèi)。
DocumentBuilderFactory的靜態(tài)方法newInstance()方法根據(jù)一個(gè)外部的環(huán)境變量javax.xml.parsers.DocumentBuilderFactory的值來(lái)確定具體生成DocumentBuilderFactory的哪一個(gè)子類(lèi)。這兒的newInstance()是一個(gè)工廠方法。當(dāng)DocumentBuilderFactory被創(chuàng)建后,可以調(diào)用其newDocumentBuilder()來(lái)創(chuàng)建具體一個(gè)DocumentBuilder的子類(lèi)。然后再由DocumentBuilder來(lái)生成Document等DOM對(duì)象。
下面是創(chuàng)建一個(gè)DOM對(duì)象的代碼片段:
1 //第一步:創(chuàng)建一個(gè)DocumentBuilderFactory。
2 DocumentBuilderFactory dbf = DocumentBuilderFactory.newInstance();
3 //第二步:創(chuàng)建一個(gè)DocumentBuilder
4 DocumentBuilder db = dbf.newDocumentBuilder();
5 //第三步:解析XML文件得到一個(gè)Document對(duì)象
6 Document doc = db.parse(new File(filename));
7
8
在這兒,DocumentBuilder,Document,Node等等對(duì)象所組成的一個(gè)產(chǎn)品組,是和具體的DocumentBuilderFactory相關(guān)的。這也就是AbstractFactory模式的含義所在。
當(dāng)然,F(xiàn)actoryMethod模式應(yīng)用的很廣。這是一個(gè)具體的例子,但他不應(yīng)該限制我們的思路,F(xiàn)actoryMethod和AbstractFactory是解決面向?qū)ο笤O(shè)計(jì)中一個(gè)基本原則--面向接口編程的主要方法。
Singleton模式
Singleton模式要解決的是對(duì)象的唯一性問(wèn)題。由Singleton模式創(chuàng)建的對(duì)象在整個(gè)的應(yīng)用程序的范圍內(nèi),只允許有一個(gè)對(duì)象的實(shí)例存在。這樣的情況在Java程序設(shè)計(jì)的過(guò)程中其實(shí)并不少見(jiàn),比如處理JDBC請(qǐng)求的連接池(Connection Pool),再比如一個(gè)全局的注冊(cè)表(Register),等等,這都需要使用到Singleton,單件模式。
在Java中,最簡(jiǎn)單的實(shí)現(xiàn)Singleton模式的方法是使用static修飾符,static可以用在內(nèi)部類(lèi)上,也可以用在方法和屬性上,當(dāng)一個(gè)類(lèi)需要被創(chuàng)建成Singleton時(shí),可以把它所有的成員都定義成static,然后再用final和private來(lái)修飾其構(gòu)造函數(shù),使其不能夠被創(chuàng)建和重載。這在程序語(yǔ)法上保證了只會(huì)有一個(gè)對(duì)象實(shí)例被創(chuàng)建。比如java.util.Math就是這樣的一個(gè)類(lèi)。
而Singleton模式所作的顯然要比上面介紹的解決方法要復(fù)雜一些,也更為安全一些。它基本的思路也還是使用static變量,但是它用一個(gè)類(lèi)來(lái)封裝這個(gè)static變量,并攔截對(duì)象創(chuàng)建方法,保證只有一個(gè)對(duì)象實(shí)例被創(chuàng)建,這兒的關(guān)鍵在于使用一個(gè)private或者protected的構(gòu)造函數(shù),而且你必須提供這樣的一個(gè)構(gòu)造函數(shù),否則編譯器會(huì)自動(dòng)的為你創(chuàng)建一個(gè)public的構(gòu)造函數(shù),這就達(dá)不到我們想要的目的了。
1 public class Singleton {
2
3 //保存唯一實(shí)例的static變量
4
5 static private Singleton _instance = null;
6
7 /*為了防止對(duì)象被創(chuàng)建,可以為構(gòu)造函數(shù)加上private修飾符,但是這同樣也防止了子類(lèi)的對(duì)象被創(chuàng)建,因而,可以選用protected修飾符來(lái)替代private。*/
8
9 protected Singleton() {
10
11 // ...
12
13 }
14
15 //static方法用來(lái)創(chuàng)建/訪問(wèn)唯一的對(duì)象實(shí)例,這兒可以對(duì)對(duì)象的創(chuàng)建進(jìn)行控制,使得可//以很容易的實(shí)現(xiàn)只允許指定個(gè)數(shù)的對(duì)象存在的泛化的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
對(duì)象創(chuàng)建的方法,除了使用構(gòu)造函數(shù)之外,還可以使用Object對(duì)象的clone()方法,因而在Singleton中也要注意這一點(diǎn)。如果Singleton類(lèi)直接繼承于Object,因?yàn)槔^承于Object的clone()方法仍保留有其protected修飾,因而不能夠被其他外部類(lèi)所調(diào)用,所以可以不用管它,但是如果Singleton繼承于一個(gè)其他的類(lèi),而這個(gè)類(lèi)又有重載clone()方法,這時(shí)就需要在Singleton中再重載clone()方法,并在其中拋出CloneNotSupportedException,這樣就可以避免多個(gè)Singleton的實(shí)例被創(chuàng)建了。
在JDK1.2以前的版本中使用Singleton模式的時(shí)候有一些需要額外注意的地方,因?yàn)镾ingleton類(lèi)并沒(méi)有被任何其他的對(duì)象所引用,所以這個(gè)類(lèi)在創(chuàng)建后一段時(shí)間會(huì)被unload,Singleton類(lèi)的靜態(tài)方法就會(huì)出現(xiàn)問(wèn)題,這是由于Java中垃圾收集機(jī)制造成的。解決的方法也很容易,只需要為其創(chuàng)建一個(gè)引用就行了。而在JDK1.2以后的版本中,Sun重新定義了Java規(guī)范,改正了其垃圾收集機(jī)制中的一些問(wèn)題,這個(gè)問(wèn)題也就不復(fù)存在了,這兒指出只是為了提起大家的主意。
Command模式用來(lái)封裝請(qǐng)求,也描敘了一致性的發(fā)送請(qǐng)求的接口,允許你配置客戶端以處理不同的請(qǐng)求,為程序增添了更大的靈活性。Singleton模式為提供對(duì)象的單一入口提供了幫助。AbstractFactory和FactoryMethod模式在功能上比較類(lèi)似,都是用來(lái)處理對(duì)象的創(chuàng)建的,但應(yīng)用在不同的層面上。在創(chuàng)建型模式中,還有Builder模式和Prototype模式,這兒不打算詳細(xì)的討論了,簡(jiǎn)單的說(shuō),Builder模式用來(lái)處理對(duì)象創(chuàng)建的細(xì)節(jié)。在兩個(gè)工廠模式中都沒(méi)有涉及到對(duì)象創(chuàng)建的具體細(xì)節(jié),都是通過(guò)接口來(lái)返回一個(gè)給定類(lèi)型的對(duì)象。而B(niǎo)uilder模式則需要對(duì)創(chuàng)建一個(gè)給定類(lèi)型對(duì)象的過(guò)程進(jìn)行建模。這對(duì)創(chuàng)建復(fù)雜對(duì)象時(shí)很有用,使得創(chuàng)建對(duì)象的算法獨(dú)立于對(duì)象各個(gè)組成部分的創(chuàng)建。而Prototype模式使用原型機(jī)制,通過(guò)創(chuàng)建簡(jiǎn)單原型的拷貝來(lái)創(chuàng)建對(duì)象。
當(dāng)初Java剛剛推出來(lái)的時(shí)候,AWT可是一個(gè)比較熱的話題,雖然現(xiàn)在有被Swing取代的趨勢(shì)。但是我一直都覺(jué)得AWT也有其優(yōu)勢(shì),至少它使用的本地代碼就要比Swing快上許多,而且,可以為用戶提供熟悉的本地操作系統(tǒng)界面。如果在Windows XP中運(yùn)行基于AWT的程序的話,XP中絢爛多變的界面Theme可以輕易應(yīng)用到AWT程序中,而Swing就不行了,因?yàn)锳WT所調(diào)用的是本帶代碼,使用的是本地的窗體控件。當(dāng)然,Swing也有其好處,不可一概而論。
簡(jiǎn)單來(lái)講,AWT提供對(duì)程序員的是對(duì)窗體界面系統(tǒng)的抽象,而在內(nèi)部實(shí)現(xiàn)中,針對(duì)每一種操作系統(tǒng),分別有不同實(shí)現(xiàn),這就是同位體(Peer)的概念。當(dāng)程序員調(diào)用AWT對(duì)象時(shí),調(diào)用被轉(zhuǎn)發(fā)到對(duì)象所對(duì)應(yīng)的一個(gè)Peer上,在由Peer調(diào)用本地對(duì)象方法,完成對(duì)象的顯示。例如,如果你使用AWT創(chuàng)建了一個(gè)Menu類(lèi)的實(shí)例,那么在程序運(yùn)行時(shí)會(huì)創(chuàng)建一個(gè)菜單同位體的實(shí)例,而由創(chuàng)建的同位體的來(lái)實(shí)際執(zhí)行菜單的現(xiàn)實(shí)和管理。不同的系統(tǒng),有不同的同位體實(shí)現(xiàn),Solaris JDK將產(chǎn)生一個(gè)Motif菜單的同位體,Windows下的JDK將產(chǎn)生一個(gè)Windows的菜單的同位體,等等。同位體的使用,使得交叉平臺(tái)窗口工具的開(kāi)發(fā)變得極為迅速,因?yàn)橥惑w的使用可以避免重新實(shí)現(xiàn)本地窗口控件中已經(jīng)包含的方法。
圖六:AWT中的組件和其對(duì)等體
實(shí)際上,從設(shè)計(jì)的角度來(lái)看,這是一個(gè)抽象和實(shí)現(xiàn)分離的過(guò)程--AWT是抽象,同位體是實(shí)現(xiàn),抽象和實(shí)現(xiàn)各自成為一個(gè)對(duì)象體系,它們由一個(gè)橋連接起來(lái),可以各自發(fā)展各自的對(duì)象層次,而不必顧慮另一方面。這就是Bridge模式所提供的思想。Bridge模式更可以提供在各個(gè)不同的實(shí)現(xiàn)中動(dòng)態(tài)的進(jìn)行切換,而不必從新編譯程序。
通常,Bridge模式和AbstractFactory模式一起工作,由AbstractFactory來(lái)創(chuàng)建一個(gè)具體實(shí)現(xiàn)的對(duì)象體系。特殊的,當(dāng)只有一個(gè)實(shí)現(xiàn)的時(shí)候,可以將Implementor抽象類(lèi)去掉。這樣,在抽象和實(shí)現(xiàn)之間建立起了一一對(duì)應(yīng)的關(guān)系,但這并不損害Bridge模式的內(nèi)涵。這被稱(chēng)為退化了的Bridge模式。
很多時(shí)候,Abstraction層次和Implementor層次之間的方法都不是一一對(duì)應(yīng)的,也就是說(shuō),在Abstraction和Implementor之不是簡(jiǎn)單的的消息轉(zhuǎn)發(fā)。通常,我們會(huì)將Abstraction作為一個(gè)抽象類(lèi)(而不是接口)來(lái)實(shí)現(xiàn)。在Implementor層次中定義底層的,或者稱(chēng)之為原子方法,而在Abstraction層次中定義一些中高層的基于原子方法的抽象方法。這樣,就能更為清晰的劃分Abstraction和Implementor,類(lèi)的結(jié)構(gòu)也更為清晰。
圖七:Bridge模式對(duì)系統(tǒng)的劃分
下面,我們來(lái)看一個(gè)Bridge模式的具體應(yīng)用??紤]這樣的一個(gè)問(wèn)題,需要生成一份報(bào)告,但是報(bào)告的格式并沒(méi)有確定,可能是HTML文件,也可能是純ASCII文本。報(bào)告本身也可能分為很多種,財(cái)務(wù)報(bào)表,貨物報(bào)表,等等問(wèn)題很簡(jiǎn)單,用繼承也較容易實(shí)現(xiàn),因?yàn)橄嗷ブg的組合關(guān)系并不是很多。但是,我們現(xiàn)在需要用Bridge的觀點(diǎn)來(lái)看問(wèn)題。
在Bridge模式中,使用一個(gè)Report類(lèi)來(lái)描敘一個(gè)報(bào)告的抽象,用一個(gè)Reporter類(lèi)來(lái)描敘Report的實(shí)現(xiàn),它的子類(lèi)有HTMLReporter和ASCIIReporter,用來(lái)分別實(shí)現(xiàn)HTML格式和ASCII格式的報(bào)告。在Report層次下面,有具體的一個(gè)StockListReport子類(lèi),用來(lái)表示貨物清單報(bào)告。
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 //抽象類(lèi)使用橋接對(duì)象的方法來(lái)實(shí)現(xiàn)一個(gè)任務(wù)
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 //實(shí)現(xiàn)層次的抽象父類(lèi)定義原子方法,供抽象層次的類(lèi)調(diào)用
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
實(shí)際上,Bridge模式是一個(gè)很強(qiáng)大的模式,可以應(yīng)用在很多方面。其基本思想:分離抽象和實(shí)現(xiàn),是設(shè)計(jì)模式的基礎(chǔ)之一。正如GOF所提到的:"找到變化的部分,并將其封裝起來(lái)";"更多的考慮用對(duì)象組合機(jī)制,而不是用對(duì)象繼承機(jī)制"。Bridge模式很好的體現(xiàn)了這幾點(diǎn)。
在使用Java中的IO類(lèi)庫(kù)的時(shí)候,是不是快要被它那些功能相似,卻又絕對(duì)可稱(chēng)得上龐雜的類(lèi)搞得要發(fā)瘋了?或許你很不明白為什么要做這么多功能相似的幾十個(gè)類(lèi)出來(lái),這就是Decorator模式將要告訴你的了。
在IO處理中,Java將數(shù)據(jù)抽象為流(Stream)。在IO庫(kù)中,最基本的是InputStream和OutputStream兩個(gè)分別處理輸出和輸入的對(duì)象(為了敘述簡(jiǎn)便起見(jiàn),這兒只涉及字節(jié)流,字符流和其完全相似),但是在InputStream和OutputStream中之提供了最簡(jiǎn)單的流處理方法,只能讀入/寫(xiě)出字符,沒(méi)有緩沖處理,無(wú)法處理文件,等等。它們只是提供了最純粹的抽象,最簡(jiǎn)單的功能。
如何來(lái)添加功能,以處理更為復(fù)雜的事情呢?你可能會(huì)想到用繼承。不錯(cuò),繼承確實(shí)可以解決問(wèn)題,但是繼承也帶來(lái)更大的問(wèn)題,它對(duì)每一個(gè)功能,都需要一個(gè)子類(lèi)來(lái)實(shí)現(xiàn)。比如,我先實(shí)現(xiàn)了三個(gè)子類(lèi),分別用來(lái)處理文件,緩沖,和讀入/寫(xiě)出數(shù)據(jù),但是,如果我需要一個(gè)既能處理文件,又具有緩沖功能的類(lèi)呢?這時(shí)候又必須在進(jìn)行一次繼承,重寫(xiě)代碼。實(shí)際上,僅僅這三種功能的組合,就已經(jīng)是一個(gè)很大的數(shù)字,如果再加上其它的功能,組合起來(lái)的IO類(lèi)庫(kù),如果只用繼承來(lái)實(shí)現(xiàn)的話,恐怕你真的是要被它折磨瘋了。
圖八:JDK中IO流的類(lèi)層次
Decorator模式可以解決這個(gè)問(wèn)題。Decorator字面的意思是裝飾的意思,在原有的基礎(chǔ)上,每添加一個(gè)裝飾,就可以增加一種功能。這就是Decorator的本意。比如,對(duì)于上面的那個(gè)問(wèn)題,只需要三個(gè)Decorator類(lèi),分別代表文件處理,緩沖和數(shù)據(jù)讀寫(xiě)三個(gè)功能,在此基礎(chǔ)上所衍生的功能,都可以通過(guò)添加裝飾來(lái)完成,而不必需要繁雜的子類(lèi)繼承了。更為重要的是,比較繼機(jī)制承而言,Decorator是動(dòng)態(tài)的,可以在運(yùn)行時(shí)添加或者去除附加的功能,因而也就具有比繼承機(jī)制更大的靈活性。
上面就是Decorator的基本思想,下面的是Decorator模式的靜態(tài)結(jié)構(gòu)圖:
圖九:Decorator模式的類(lèi)圖
可以看到,一個(gè)Decorator與裝飾的Subject對(duì)象有相同的接口,并且除了接口中給出的方法外,每個(gè)Decorator均有自己添加的方法,來(lái)添加對(duì)象功能。每個(gè)Decorator均有一個(gè)指向Subject對(duì)象的引用,附加的功能被添加在這個(gè)Subject對(duì)象上。而Decorator對(duì)象本身也是一個(gè)Subject對(duì)象,因而它也能夠被其他的Decorator所修飾,提供組合的功能。
在Java IO操作中,經(jīng)??梢钥吹街T如如下的語(yǔ)句:
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
多個(gè)的Decorator被層疊在一起,最后得到一個(gè)功能強(qiáng)大的流。既能夠被緩沖,又能夠得到行數(shù),這就是Decorator的威力!
不僅僅如此,Java中的IO還允許你引入自定義的Decorator,來(lái)實(shí)現(xiàn)自己想要的功能。在良好的設(shè)計(jì)背景下,這做起并不復(fù)雜,只需要4步:
創(chuàng)建兩個(gè)分別繼承了FilterInputStream和 FilterOutputStream的子類(lèi)
重載read()和write()方法來(lái)實(shí)現(xiàn)自己想要的功能。
可以定義或者重載其它方法來(lái)提供附加功能。
確定這兩個(gè)類(lèi)會(huì)被一起使用,因?yàn)樗鼈冊(cè)诠δ苌鲜菍?duì)稱(chēng)的。
就這樣,你就可以無(wú)限的擴(kuò)展IO的功能了。
在了解了IO中的Decorator后,我們?cè)賮?lái)看一個(gè)Decorator模式應(yīng)用的具體的例子。這個(gè)例子原本是出現(xiàn)在GOF書(shū)中的,這兒稍作改動(dòng),引來(lái)示例。
在一個(gè)圖形用戶界面(GUI)中,一個(gè)組件有時(shí)候需要用到邊框或者滾動(dòng)條,而有時(shí)候又不需要,有時(shí)候可能兩者都要用到。當(dāng)需要?jiǎng)討B(tài)的去處或者添加職能的時(shí)候,就可以考慮使用Decorator模式了。這兒對(duì)于一個(gè)VisualComponent組件對(duì)象,我們引入了兩個(gè)Decorator類(lèi):BoderDecorator和ScrollDecorator,分別用來(lái)為組件添加邊框和處理滾動(dòng)。程序類(lèi)圖如下:
圖十:Decorator模式的應(yīng)用例子
程序?qū)懙煤芎?jiǎn)單,沒(méi)有包括具體的代碼,只是有一個(gè)可以運(yùn)行的框架以供參考。代碼如下:
1 //Client類(lèi)用來(lái)創(chuàng)建窗體和組件對(duì)象,這兒可以看到Decorator是如何組合和應(yīng)用的
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類(lèi)用來(lái)容納組件對(duì)象
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類(lèi)定義了組件的接口
38
39 class VisualComponent{
40
41 public VisualComponent (){}
42
43 public void draw (){}
44
45 public void resize (){}
46
47 }
48
49 //TextView類(lèi)是一個(gè)顯示文本的具體的組件
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類(lèi)繼承于VisualComponent,定義所有Decorator的缺省方法實(shí)現(xiàn)
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類(lèi)為組件提供邊框
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類(lèi)為組件提供滾動(dòng)條
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確實(shí)能夠很好的緩解當(dāng)功能組合過(guò)多時(shí)子類(lèi)繼承所能夠帶來(lái)的問(wèn)題。但是在得到很大的靈活性的同時(shí),Decorator在使用時(shí)也表現(xiàn)得較為復(fù)雜??纯磧H僅為了得到一個(gè)IO流,除了要?jiǎng)?chuàng)建核心的流外,還要為其加上各種各樣的裝飾類(lèi),這使得代碼變得復(fù)雜而難懂。有幾個(gè)人一開(kāi)始時(shí)沒(méi)有被Java的IO庫(kù)嚇一跳呢?
Bridge模式用來(lái)分離抽象和實(shí)現(xiàn),使得這兩個(gè)部分能夠分別的演化而不必修改另外一部分的內(nèi)容。通常的,可以在實(shí)現(xiàn)部分定義一些基本的原子方法,而在抽象部分則通過(guò)組合定義在實(shí)現(xiàn)層次中的原子方法來(lái)實(shí)現(xiàn)系統(tǒng)的功能。Decorator模式通過(guò)聚合機(jī)制來(lái)為對(duì)象動(dòng)態(tài)的添加職責(zé),解決了在子類(lèi)繼承中容易引起的子類(lèi)爆炸的問(wèn)題。
毫無(wú)疑問(wèn)的,AWT中的Component-Container體系就是一個(gè)很好的Composite模式的例子。Container繼承于Component,而Container中有可以包含有多個(gè)Component,因?yàn)镃ontainer實(shí)際上也是Component,因而Container也可以包含Container。這樣通過(guò)Component-Container結(jié)構(gòu)的對(duì)象組合,形成一個(gè)樹(shù)狀的層次結(jié)構(gòu)。這也就是Composite模式所要做的。
Composite模式是為了簡(jiǎn)化編程而提出的,一般的在編程的時(shí)候,如果嚴(yán)格的區(qū)分Component和Container的話,有時(shí)候會(huì)帶來(lái)許多不便,而且這些往往是沒(méi)有必要的。比如,我要在一個(gè)Container中放置一個(gè)Component,我并不需要知道這個(gè)Component到底是一個(gè)Container,或者就是一個(gè)一般的Component,在父級(jí)容器中所要做的,只是記錄一個(gè)Component的引用,在需要的時(shí)候調(diào)用Component的繪制方法來(lái)顯示這個(gè)Component。當(dāng)這個(gè)Component確實(shí)是一個(gè)Container的時(shí)候,它可以通過(guò)Container重載后的繪制方法,完成對(duì)這個(gè)容器的顯示,并把繪制消息傳遞給到它的子對(duì)象去。也就是說(shuō),對(duì)一個(gè)父級(jí)容器而言,它并不不關(guān)心,其子對(duì)象到底是一個(gè)Component,還是一個(gè)Container。它需要將Component和Container統(tǒng)一對(duì)待。
圖十一:Composite模式的類(lèi)圖
Composite模式比較簡(jiǎn)單,實(shí)現(xiàn)起來(lái)也不復(fù)雜,但是有一定的局限性。比如,在處理樹(shù)的時(shí)候,我們往往需要處理三類(lèi)對(duì)象:子樹(shù),頁(yè)節(jié)點(diǎn)和非頁(yè)節(jié)點(diǎn)。而在Composite模式中對(duì)于子樹(shù)和非葉節(jié)點(diǎn)的區(qū)分并不明顯,而是把他們合成為一個(gè)Composite對(duì)象了。而且在GOF給出的Composite的模式中,對(duì)于添加,刪除子節(jié)點(diǎn)等屬于Composite對(duì)象的的方法,是放在了Component對(duì)象中的,這雖然在實(shí)現(xiàn)的時(shí)候可以區(qū)分開(kāi)來(lái),但容易造成一些概念上的誤解。
由上所敘,我們可以提出一個(gè)改進(jìn)了的Composite模式,引入子樹(shù)對(duì)象,從而將子樹(shù)和非葉節(jié)點(diǎn)分開(kāi),如下圖所示:
圖十二:Composite模式的一種變體
雖然將Composite從Component類(lèi)層次中分離出來(lái),但并沒(méi)有損害Composite模式的內(nèi)涵。這樣做不一定就會(huì)比上面的那個(gè)要好,各有不同的應(yīng)用,不過(guò)有時(shí)候用這樣的方法來(lái)處理子樹(shù)要容易些,概念上也更為清晰。
下面的代碼,給出了一個(gè)Composite模式簡(jiǎn)單的Java實(shí)現(xiàn):
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模式主要用來(lái)將算法實(shí)現(xiàn)從類(lèi)中分離出來(lái),并封裝在一個(gè)單獨(dú)的類(lèi)中。更簡(jiǎn)單的說(shuō),對(duì)象與其行為(behaviour)這本來(lái)緊密聯(lián)系的兩部分被解耦,分別放在了兩個(gè)不同的類(lèi)中。這使得對(duì)同一個(gè)行為,可以方便的在任何時(shí)候切換不同的實(shí)現(xiàn)算法。而通過(guò)對(duì)策略的封裝,為其提供統(tǒng)一的接口,也可以很容易的引入新的策略。
AWT的LayoutManager,是Strategy模式的一個(gè)例子。對(duì)于GUI而言,每個(gè)組件(Component)在容器中(Container)的排放是需要遵循一定的算法的。通常的方法是使用絕對(duì)坐標(biāo),就像VB,Delphi之類(lèi)的工具所作的那樣,記錄每個(gè)組件在容器中的位置。這當(dāng)然會(huì)帶來(lái)一些問(wèn)題,比如在窗體縮放的時(shí)候,就需要手工編碼改變組件的大小和位置,以使得原來(lái)的比例得以保存。而在AWT中,引入了布局管理器(LayoutManager)的概念,使得布局的方法大大豐富,編碼過(guò)程也變得簡(jiǎn)單。
一個(gè)容器,比如Applet,Panel等,僅僅記錄其包含的組件,而布局管理器中封裝了對(duì)容器中組件進(jìn)行布局的算法,具體地說(shuō),就是指明容器中組件的位置和尺寸的大小。通過(guò)布局管理器,你只需要確定想放置的組件間的相對(duì)位置即可,這一方面簡(jiǎn)化編碼,另一方面也有助于實(shí)現(xiàn)
軟件 的平臺(tái)無(wú)關(guān)性。
圖十三:AWT中的容器和布局管理器的關(guān)系
每一個(gè)容器均有一個(gè)布局管理器,當(dāng)容器需要布置它的組件時(shí),它調(diào)用布局管理器的方法布置容器內(nèi)的組件。LayoutManager2繼承于LayoutManager,提供更為細(xì)致的布局功能,它可以讓布局管理器為組件加上約束條件已確定組件如何被布置。例如,為了確定組件被擺放在邊框內(nèi)的位置,BorderLayout在它的組件上加上方向指示。
特別的,通過(guò)實(shí)現(xiàn)LayoutManager或者LayoutManager2接口,可以很容易實(shí)現(xiàn)自定義的布局策略。
回到模式的話題上來(lái),如果有幾個(gè)很相似的類(lèi),其區(qū)別僅僅是在個(gè)別行為上的動(dòng)作不同,這時(shí)候就可以考慮使用Strategy模式。這樣,通過(guò)策略組合,將原來(lái)的多個(gè)類(lèi)精簡(jiǎn)為一個(gè)帶有多個(gè)策略的類(lèi)。這很符合OO設(shè)計(jì)的原則:找到變化的部分,并將其封裝起來(lái)!Strategy模式同樣的為子類(lèi)繼承提供了一個(gè)好的替代方案,當(dāng)使用繼承機(jī)制的時(shí)候,行為的改變是靜態(tài)的,你指能夠改變一次--而策略是動(dòng)態(tài)的,可以在任何時(shí)候,切換任何次數(shù)。更為重要的是,策略對(duì)象可以在不同的環(huán)境中被不同的對(duì)象所共享。以布局管理器為例,雖然每一個(gè)容器只有一個(gè)布局管理器,但是一個(gè)布局管理器可以為多個(gè)容器工作。
圖十四:Strategy模式的類(lèi)圖
Strategy模式也有一些缺點(diǎn),比如,應(yīng)用程序必須知道所有的策略對(duì)象,并從中選者其一。而且在策略對(duì)象被使用的時(shí)候,它和Context對(duì)象之間通常是緊耦合的,Context對(duì)象必須為策略對(duì)象提供與具體算法相關(guān)的數(shù)據(jù)或者其它的東西,而這些數(shù)據(jù)的傳遞可能并不能夠風(fēng)裝載抽象地策略類(lèi)中,因?yàn)椴⒉皇撬械乃惴ǘ紩?huì)需要這些數(shù)據(jù)的。另外,因?yàn)椴呗詫?duì)象通常由應(yīng)用程序所創(chuàng)建,Context對(duì)象并不能夠控制Strategy的生命期,而在概念上,這個(gè)策略應(yīng)該從屬于Context對(duì)象,其生命期不應(yīng)該超出Context的范圍對(duì)象。
通常的,Strategy很容易和Bridge模式相混淆。確實(shí),他們有著很相近的結(jié)構(gòu),但是,他們卻是為解決不同的問(wèn)題而設(shè)計(jì)的。Strategy模式注重于算法的封裝,而B(niǎo)ridge模式注重于分離抽象和實(shí)現(xiàn),為一個(gè)抽象體系提供不同的實(shí)現(xiàn)。
Iterator模式用來(lái)規(guī)格化對(duì)某一數(shù)據(jù)結(jié)構(gòu)的遍歷接口。
JDK中在Collection Framework中引入了Iterator接口,提供對(duì)一個(gè)Collection的遍歷。每一個(gè)Collection類(lèi)中都定義有從Collection接口中繼承而來(lái)的iterator()方法,來(lái)得到一個(gè)Iterator對(duì)象,我們稱(chēng)之為遍歷器,Iterator接口很簡(jiǎn)單:
hasNext():用來(lái)判斷在遍歷器中是否還有下一個(gè)元素。
next():返回遍歷器中的下一個(gè)元素。
remove():在被遍歷的Collection類(lèi)中刪除最后被返回的那個(gè)對(duì)象。
我們就以最為常用的Vector為例,看看在Collection Framework中,Iterator模式是如何被實(shí)現(xiàn)的。在此之前,我們需要先了解一些Vector和Collection Framework的結(jié)構(gòu)。
Collection接口作為這個(gè)Framework的基礎(chǔ),被所有其它的集合類(lèi)所繼承或者實(shí)現(xiàn)。對(duì)Collection接口,有一個(gè)基本的實(shí)現(xiàn)是抽象類(lèi)AbstractCollection,它實(shí)現(xiàn)了大部分與具體數(shù)據(jù)結(jié)構(gòu)無(wú)關(guān)的操作。比如判斷一個(gè)對(duì)象是否存在于這個(gè)集合類(lèi)中的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
而這其中調(diào)用的iterator()方法是一個(gè)抽象方法,有賴(lài)于具體的數(shù)據(jù)結(jié)構(gòu)的實(shí)現(xiàn)。但是對(duì)于這個(gè)containers()方法而言,并不需要知道具體的Iterator實(shí)現(xiàn),而只需要知道它所提供的接口,能夠完成某類(lèi)任務(wù)既可,這就是抽象類(lèi)中抽象方法的作用。其它的在AbstractCollection中實(shí)現(xiàn)的非抽象方法,大部分都是依賴(lài)于抽象方法iterator()方法所提供的Iterator接口來(lái)實(shí)現(xiàn)的。這種設(shè)計(jì)方法是引入抽象類(lèi)的一個(gè)關(guān)鍵所在,值得仔細(xì)領(lǐng)悟。
List接口繼承Collection接口,提供對(duì)列表集合類(lèi)的抽象;對(duì)應(yīng)的AbstractList類(lèi)繼承AbstractCollection,并實(shí)現(xiàn)了List接口,作為L(zhǎng)ist的一個(gè)抽象基類(lèi)。它對(duì)其中非抽象方法的實(shí)現(xiàn),也大抵上與AbstractCollection相同,這兒不再贅敘。
而對(duì)應(yīng)于Collection的Iterator,List有其自己的ListIterator,ListIterator繼承于Iterator,并添加了一些專(zhuān)用于List遍歷的方法:
boolean hasPrevious():判斷在列表中當(dāng)前元素之前是否存在有元素。
Object previous():返回列表中當(dāng)前元素之前的元素。
int nextIndex():
int previousIndex():
void set(Object o):
void add(Object o):
ListIterator針對(duì)List,提供了更為強(qiáng)勁的功能接口。在AbstractList中,實(shí)現(xiàn)了具體的iterator()方法和listIterator()方法,我們來(lái)看看這兩個(gè)方法是如何實(shí)現(xiàn)的:
1 public Iterator iterator() {
2
3 return new Itr(); //Itr是一個(gè)內(nèi)部類(lèi)
4
5 }
6
7 private class Itr implements Iterator {
8
9 int cursor = 0;//Iterator的計(jì)數(shù)器,指示當(dāng)前調(diào)用next()方法時(shí)會(huì)被返回的元素的位置
10
11 int lastRet = -1;//指示剛剛通過(guò)next()或者previous()方法被返回的元素的位置,-1
12
13 //表示剛剛調(diào)用的是remove()方法刪除了一個(gè)元素。
14
15 //modCount是定義在AbstractList中的字段,指示列表被修改的次數(shù)。Iterator用//這個(gè)值來(lái)檢查其包裝的列表是否被其他方法所非法修改。
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方法仍然是一個(gè)抽象方法,依賴(lài)于具體的子類(lèi)實(shí)現(xiàn)
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)也依賴(lài)于具體的子類(lèi)實(shí)現(xiàn)
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
這兒的設(shè)計(jì)技巧和上面一樣,都是使用抽象方法來(lái)實(shí)現(xiàn)一個(gè)具體的操作。抽象方法作為最后被實(shí)現(xiàn)的內(nèi)容,依賴(lài)于具體的子類(lèi)。抽象類(lèi)看起來(lái)很像是一個(gè)介于接口和子類(lèi)之間的一個(gè)東西。
從設(shè)計(jì)上來(lái)講,有人建議所有的類(lèi)都應(yīng)該定義成接口的形式,這當(dāng)然有其道理,但多少有些極端。當(dāng)你需要最大的靈活性的時(shí)候,應(yīng)該使用接口,而抽象類(lèi)卻能夠提供一些缺省的操作,最大限度的統(tǒng)一子類(lèi)。抽象類(lèi)在許多應(yīng)用框架(Application Framework)中有著很重要的作用。例如,在一個(gè)框架中,可以用抽象類(lèi)來(lái)實(shí)現(xiàn)一些缺省的服務(wù)比如消息處理等等。這些抽象類(lèi)能夠讓你很容易并且自然的把自己的應(yīng)用嵌入到框架中去。而對(duì)于依賴(lài)于每個(gè)應(yīng)用具體實(shí)現(xiàn)的方法,可以通過(guò)定義抽象方法來(lái)引入到框架中。
其實(shí)在老版本的JDK中也有類(lèi)似的概念,被稱(chēng)為Enumeration。Iterator其實(shí)與Enmeration功能上很相似,只是多了刪除的功能。用Iterator不過(guò)是在名字上變得更為貼切一些。模式的另外一個(gè)很重要的功用,就是能夠形成一種交流的語(yǔ)言(或者說(shuō)文化)。有時(shí)候,你說(shuō)Enumeration大家都不明白,說(shuō)Iterator就都明白了。
Composite,Strategy和Iterator。Composite是一個(gè)結(jié)構(gòu)性的模式,用來(lái)協(xié)調(diào)整體和局部的關(guān)系,使之能夠被統(tǒng)一的安排在一個(gè)樹(shù)形的結(jié)構(gòu)中,并簡(jiǎn)化了編程。Strategy模式與Bridge模式在結(jié)構(gòu)上很相似,但是與Bridge不同在于,它是一個(gè)行為模式,更側(cè)重于結(jié)構(gòu)的語(yǔ)義以及算法的實(shí)現(xiàn)。它使得程序能夠在不同的算法之間自由方便的作出選擇,并能夠在運(yùn)行時(shí)切換到其他的算法,很大程度上增加了程序的靈活性。Iterator模式提供統(tǒng)一的接口操作來(lái)實(shí)現(xiàn)對(duì)一個(gè)數(shù)據(jù)結(jié)構(gòu)的遍歷,使得當(dāng)數(shù)據(jù)結(jié)構(gòu)的內(nèi)部算法發(fā)生改變時(shí),客戶代碼不需要任何的變化,只需要改變相應(yīng)的Iterator實(shí)現(xiàn),就可以無(wú)縫的集成在原來(lái)的程序中。
有了前面諸多設(shè)計(jì)模式的基礎(chǔ),這兒可以提出一個(gè)比較特殊的模式MVC。MVC并不屬于GOF的23個(gè)設(shè)計(jì)模式之列,但是它在GOF的書(shū)中作為一個(gè)重要的例子被提出來(lái),并給予了很高的評(píng)價(jià)。一般的來(lái)講,我們認(rèn)為GOF的23個(gè)模式是一些中級(jí)的模式,在它下面還可以抽象出一些更為一般的低層的模式,在其上也可以通過(guò)組合來(lái)得到一些高級(jí)的模式。MVC就可以看作是一些模式進(jìn)行組合之后的結(jié)果(實(shí)際上,MVC的出現(xiàn)要早于設(shè)計(jì)模式的提出,這而只是對(duì)它在設(shè)計(jì)模式的基礎(chǔ)上進(jìn)行在分析)。如果沒(méi)有前面的基礎(chǔ),理解MVC或許會(huì)有一些困難。
MVC模式
MVC模式比較的特別,它含義比較的廣,涉及的層面也不僅僅是設(shè)計(jì)這一塊,不好簡(jiǎn)單的把它歸為設(shè)計(jì)模式。當(dāng)然,它主要還是作為一個(gè)設(shè)計(jì)的概念被提到的,而且在Java體系中,MVC有著至關(guān)重要的作用。這兒提的是Java中的設(shè)計(jì)模式,當(dāng)然不好拉了它不講了。
關(guān)于MVC的來(lái)龍去脈,這兒就不再講了。這里主s要講兩個(gè)方面的:作為設(shè)計(jì)模式的MVC和作為體系結(jié)構(gòu)模式的MVC。
所謂MVC,指的是一種劃分系統(tǒng)功能的方法,它將一個(gè)系統(tǒng)劃分為三個(gè)部分:
模型(Model):封裝的是數(shù)據(jù)源和所有基于對(duì)這些數(shù)據(jù)的操作。在一個(gè)組件中,Model往往表示組件的狀態(tài)和操作狀態(tài)的方法。
視圖(View):封裝的是對(duì)數(shù)據(jù)源Model的一種顯示。一個(gè)模型可以由多個(gè)視圖,而一個(gè)視圖理論上也可以同不同的模型關(guān)聯(lián)起來(lái)。
控制器(Control):封裝的是外界作用于模型的操作。通常,這些操作會(huì)轉(zhuǎn)發(fā)到模型上,并調(diào)用模型中相應(yīng)的一個(gè)或者多個(gè)方法。一般Controller在Model和View之間起到了溝通的作用,處理用戶在View上的輸入,并轉(zhuǎn)發(fā)給Model。這樣Model和View兩者之間可以做到松散耦合,甚至可以彼此不知道對(duì)方,而由Controller連接起這兩個(gè)部分。
有了前面介紹的諸多模式之后,就可以很容易的通過(guò)模式來(lái)解釋MVC的內(nèi)在行為了。前面說(shuō)過(guò),在設(shè)計(jì)模式中,MVC實(shí)際上是一個(gè)比較高層的模式,它由多個(gè)更基本的設(shè)計(jì)模式組合而成,Model-View的關(guān)系實(shí)際上是Observer模式,模型的狀態(tài)和試圖的顯示相互響應(yīng),而View-Controller則是由Strategy模式所描敘的,View用一個(gè)特定的Controller的實(shí)例來(lái)實(shí)現(xiàn)一個(gè)特定的響應(yīng)策略,更換不同的Controller,可以改變View對(duì)用戶輸入的響應(yīng)。而其它的一些設(shè)計(jì)模式也很容易組合到這個(gè)體系中。比如,通過(guò)Composite模式,可以將多個(gè)View嵌套組合起來(lái);通過(guò)FactoryMethod模式來(lái)指定View的Controller,等等。
使用MVC的好處,一方面,分離數(shù)據(jù)和其表示,使得添加或者刪除一個(gè)用戶視圖變得很容易,甚至可以在程序執(zhí)行時(shí)動(dòng)態(tài)的進(jìn)行。Model和View能夠單獨(dú)的開(kāi)發(fā),增加了程序了可維護(hù)性,可擴(kuò)展性,并使測(cè)試變得更為容易。另一方面,將控制邏輯和表現(xiàn)界面分離,允許程序能夠在運(yùn)行時(shí)根據(jù)工作流,用戶習(xí)慣或者模型狀態(tài)來(lái)動(dòng)態(tài)選擇不同的用戶界面。
Swing號(hào)稱(chēng)是完全按照MVC的思路來(lái)進(jìn)行設(shè)計(jì)的。在設(shè)計(jì)開(kāi)始前,Swing的希望能夠達(dá)到的目標(biāo)就包括:
模型
驅(qū)動(dòng) (Model-Driven)的編程方式。
提供一套單一的API,但是能夠支持多種視感(look-and-feel),為用戶提供不同的界面。
很自然的可以發(fā)現(xiàn),使用MVC模式能夠有助于實(shí)現(xiàn)上面的這兩個(gè)目標(biāo)。
嚴(yán)格的說(shuō),Swing中的MVC實(shí)際上是MVC的一個(gè)變體:M-VC。 Swing中只顯示的定義了Model接口,而在一個(gè)UI對(duì)象中集成了視圖和控制器的部分機(jī)制。View和Control比較松散的交叉組合在一起,而更多的控制邏輯是在事件監(jiān)聽(tīng)者部分引入的。
但是,這并沒(méi)有妨礙在Swing中體現(xiàn)MVC的精髓。事實(shí)上,在Swing的開(kāi)發(fā)初期,Swing確實(shí)是按照標(biāo)準(zhǔn)的MVC模式來(lái)設(shè)計(jì)的,但是很快的問(wèn)題就出現(xiàn)了:View和Controller實(shí)際上是緊密耦合的,很難作出一個(gè)能夠適應(yīng)不同View的一般化的Controller來(lái),而且,一般也沒(méi)有很大的必要。
在Swing中基本上每一個(gè)組件都會(huì)有對(duì)應(yīng)的Model對(duì)象。但其并不是一一對(duì)應(yīng)的,一個(gè)Model接口可以為多個(gè)Swing對(duì)向服務(wù),例如:JProgressBar,JScrollBar,JSlider這三個(gè)組件使用的都是BoundedRangeModel接口。這種模型的共享更能夠從分的體現(xiàn)MVC的內(nèi)涵。除了Model接口外,為了實(shí)現(xiàn)多個(gè)視感間的自由切換,每個(gè)Swing組件還包含一個(gè)UI接口--也就是View-Controller,負(fù)責(zé)對(duì)組件的繪制和接受用戶輸入。
Model-View是Subject和Obverser的關(guān)系,因而,模型的改變必須要在UI對(duì)象中體現(xiàn)出來(lái)。Swing使用了JavaBeans的事件模型來(lái)實(shí)現(xiàn)這種通知機(jī)制。具體而言,有兩種實(shí)現(xiàn)辦法,一是僅僅通知事件監(jiān)聽(tīng)者狀態(tài)改變了,然后由事件監(jiān)聽(tīng)者向模型提取必要的狀態(tài)信息。這種機(jī)制對(duì)于事件頻繁的組件很有效。另外的一種辦法是模型向監(jiān)聽(tīng)者發(fā)送包含了已改變的狀態(tài)信息的通知給UI。這兩種方法根據(jù)其優(yōu)劣被分別是現(xiàn)在不同的組件中。比如在JScollBar中使用的是第一種方法,在JTable中使用的是第二種方法。而對(duì)Model而言,為了能夠支持多個(gè)View,它并不知道具體的每一個(gè)View。它維護(hù)一個(gè)對(duì)其數(shù)據(jù)感興趣的Obverser的列表,使得當(dāng)數(shù)據(jù)改變的時(shí)候,能夠通知到每一個(gè)Swing組件對(duì)象。
上面講到的是作為設(shè)計(jì)模式的MVC。而在J2EE中,Sun更是將MVC提升到了一個(gè)體系結(jié)構(gòu)模式的高度,這兒的MVC的含義就更為廣泛了。與Swing中不同的是,在這兒MVC的各個(gè)部件不再是單純的類(lèi)或者接口,而是應(yīng)用程序的一個(gè)組成部分!
在J2EE Blueprint中,Sun推薦了一種基于MVC的J2EE程序的模式。對(duì)于企業(yè)級(jí)的分布式應(yīng)用程序而言,它更需要支持多種形式的用戶接口。比如,網(wǎng)上商店需要一個(gè)HTML的界面來(lái)同網(wǎng)上的客戶打交道,WML的界面可以提供給無(wú)線用戶,管理者可能需要傳統(tǒng)的基于Swing的應(yīng)用程序來(lái)進(jìn)行管理,而對(duì)對(duì)商業(yè)伙伴,基于XML的Web服務(wù)可能對(duì)他們更為方便。
MVC無(wú)疑是這樣一個(gè)問(wèn)題的有效的解決方法,通過(guò)在控制和顯示邏輯分離出核心的數(shù)據(jù)存取功能,形成一個(gè)Model模塊,能夠讓多種視圖來(lái)共享這個(gè)Model。
在J2EE中有幾個(gè)核心的技術(shù),JSP,JavaBean,Servlet,EJB,SessionBean,EntityBean構(gòu)成了J2EE構(gòu)架的基石。JSP能夠生成HTML,WML甚至XML,它對(duì)應(yīng)于Web應(yīng)用程序中的View部分。EJB作為數(shù)據(jù)庫(kù)與應(yīng)用程序的中介,提供了對(duì)數(shù)據(jù)的封裝。一般EntityBean封裝的是數(shù)據(jù),SessionBean是封裝的是對(duì)數(shù)據(jù)的操作。這兩個(gè)部分合起來(lái),對(duì)應(yīng)于Web應(yīng)用程序的Model部分。在技術(shù)上,JSP能夠直接對(duì)EJB進(jìn)行存取,但這并不是好辦法,那樣會(huì)混淆程序中的顯示邏輯和控制邏輯,使得JSP的重用性能降低。這時(shí)候有兩種解決方法,通過(guò)JavaBean或者Servlet作為中介的控制邏輯,對(duì)EJB所封裝的數(shù)據(jù)進(jìn)行存取。這時(shí),JavaBean或者Servlet對(duì)應(yīng)于Web引用程序中的Controller部分。兩種類(lèi)型的Controller各有其優(yōu)缺點(diǎn):JSP同Servlet的交互不容易規(guī)范化,使得交互的過(guò)程變得復(fù)雜,但是Servlet可以單獨(dú)同用戶交互,實(shí)際上JSP的運(yùn)行時(shí)狀態(tài)就是Servlet;而由于JavaBean的規(guī)范性,JSP同JavaBean的交互很容易,利用JavaBean的get/set方法,JSP不需要過(guò)多的語(yǔ)句就可以完成數(shù)據(jù)的存取,這能夠讓JSP最大限度的集中在其視圖功能上,而且,在桌面應(yīng)用程序中使用JavaBean也很容易,而用Servlet就相對(duì)麻煩許多。根據(jù)不同的問(wèn)題背景,可以選取不同的Controller,有時(shí)候也可以兩者混合使用,或者直接在Servlet中調(diào)用JavaBean。
在J2EE中,MVC是一個(gè)大的框架,這時(shí)我們往往把它不再看作為設(shè)計(jì)模式,而是作為體系結(jié)構(gòu)模式的一個(gè)應(yīng)用了。
總結(jié)
在這篇文章中,從設(shè)計(jì)的角度,對(duì)Java的類(lèi)庫(kù)進(jìn)行了一些分析,并著重于設(shè)計(jì)模式在其中的使用問(wèn)題。相信大家看了之后,不論對(duì)Java類(lèi)庫(kù)本身,還是設(shè)計(jì)模式,都應(yīng)該有了一個(gè)更深層次的了解。當(dāng)然,Java類(lèi)庫(kù)是一個(gè)非常龐大的東西,還有著許多設(shè)計(jì)精良的結(jié)構(gòu)。因而,對(duì)Java源代碼的研究,不論對(duì)于編碼還是設(shè)計(jì),都是很有裨益的。本人接觸設(shè)計(jì)模式的時(shí)間并不很長(zhǎng),對(duì)其的理解可能會(huì)有一些偏差,如有謬誤的地方,還請(qǐng)能夠提出,大家能夠共同的探討。
需要說(shuō)明的是,對(duì)模式的描敘實(shí)際上是有一套完整的規(guī)格(或者語(yǔ)言)來(lái)進(jìn)行的,涉及到模式的意圖(Intent),問(wèn)題描敘(Problem),背景(Context),約束(Force),解決方案(Solution),結(jié)果(Resulting Context)等等。但這兒為了敘述的方便,并沒(méi)有將它們一一列舉。如果需要對(duì)模式有詳細(xì)系統(tǒng)的研究,就應(yīng)該對(duì)這些規(guī)格敘述有更深入的了解。