近期寫了個電子書的C/S模式的下載工具,一個server端,一個client端。
目的就是想在公司能很方便的訪問家里那些收集很久電子書,方便查閱。
用了1,2個星期,雖然寫的很爛,但是沒有用任何第三方的產(chǎn)品(server or db)。
現(xiàn)在里面的書籍已經(jīng)接近200本了。
注:server就用了家里的adsl,所以速度慢,關(guān)閉不定時。畢竟玩玩嘛。
有興趣的朋友先裝個jdk1.5。再運行下面壓縮包里的exe文件執(zhí)行即可。
點此下載
User ID:???????????????blogjava
Password:???????????? blogjava?
Java Collection Framwork中的類的確是最重要的基礎(chǔ)api,實現(xiàn)任何算法,基本上都很難離開它。
因此理解這堆“集合(Collection)類”很有必要。聲明一下,以前一直都是叫它們集合類,但是好像Think In Java的作者鄙視了這個說法,嚴格的說應(yīng)該叫Container類,而后看了它整整一章書以后,覺得還是人家說的有道理。
它說這個container類庫,包含了兩大類,Collection和Map,而Collection又可以分為List和Set。當(dāng)然這些抽象概念都被定義成了接口。
話說,這樣的分類的確是嚴格按照類之間的繼承關(guān)系來說得,但是俺總覺得很別扭,真動手的時候,還是很難選擇。當(dāng)然,Anytime and Anywhere使用ArrayList絕對都能解決問題,但這樣做畢竟太農(nóng)民了一點。
所以,我自己有了一些想法。先回歸到最基本最基本的數(shù)據(jù)結(jié)構(gòu)的層面,管你是Collection還是Container,反正描述的都是一堆東西吧。數(shù)據(jù)結(jié)構(gòu)第一章講了一個結(jié)構(gòu):在物理上連續(xù)分配空間的順序結(jié)構(gòu),叫順序表(希望記性是好的),而離散分配空間的,應(yīng)該叫做鏈表,最常用的就是單鏈表。這兩個東西,其實就是很多復(fù)雜數(shù)據(jù)結(jié)構(gòu)的基礎(chǔ),還記得嗎,當(dāng)時就是講完這些東西,才開始講棧、隊列、二叉樹、有向無向圖的。所以,這個順序結(jié)構(gòu)是很基礎(chǔ)的。而在JAVA中,順序表對應(yīng)的就是List接口,而一般順序表就是ArrayList(有效進行隨機index查找);而單鏈表就是LinkedList(有效進行插入和刪除),兩個的優(yōu)劣當(dāng)年都講爛了,這里就不說了。
有了這兩個結(jié)構(gòu)以后,JAVA就不提供Stack和Queue單獨的類了,因為,用戶可以用上面兩個類輕易的去實現(xiàn)。
那Set和Map有怎么跟List連上關(guān)系呢?
我認為可以把它們看成是無序和單一的List(Map只是兩個有映射關(guān)系的List罷了)。
Set和Map無序和單一的特性,決定了它們天大的需求就是根據(jù)關(guān)鍵字(元素對象)檢索。so,為了效率,必須hash。
有了HashSet和HashMap。
同時,如果非要保持住元素的順序,有了LinkedHashSet、LinkedHashMap。
結(jié)論:
假如你的需求是
1:往Container中放的對象是無序且單一的;
2:經(jīng)常要檢索。
用HashSet或HashMap吧。
ps:這兩個條件其實是一回事,因為如果是不單一的話,你去檢索它干嘛。
如果進而需要保持元素的順序,不要讓他順便iteration,那就選擇LinkedHashSet和LinkedHashMap。
假如你的需求不滿足以上1&2,那你放心,List肯定能幫你解決,你只要稍微想一下是ArrayList好還是LinkedList好。
題外話:
關(guān)于Hash,務(wù)必記得要讓自己的元素對象override hashCode()和 equles() 方法,要不你直接可以洗了睡。
關(guān)于所有這些Container,務(wù)必記得有個輔助類叫Interator,遍歷盡量要用它。
關(guān)于一些老的Stack、Vector、HashTable,聽說以后不要用了哦。收到啦!!
任何信息,基本都是以文字的形式傳播和記錄下來的。
在計算機中,文字就是字符的集合,也就是字符串,C就是因為對字符串設(shè)計的不好,才那么容易溢出。而別的一些高級語言,對于這個進行了很多的改進。
編程的人由于技術(shù)方向和應(yīng)用方向的不同,日常編程的內(nèi)容差距很大。但是對于字符串的處理,那可是永遠都避不開的工作。
昨天跑步的時候,想了一下,對于字符串的操作有那么多(search,match,split,replace),感覺很煩雜,能不能抓住這些操作的一個基本集?
不知道對不對,反正想出來了一個,這個基本操作就是search,這里的search的意思是:在輸入串中找到目標(biāo)串的開始位置(start index),和結(jié)束位置(end index)。
有了這個基本集,別的操作都很好衍生出來:
局部match:其實就是要求search操作至少返回一個start index。
全match:其實要求search操作的至少返回一個start index,并且start index要為零,end index要為輸入串的全長。
split:其實就是search操作之后,把前一個end index和當(dāng)前的start index之間的字符串截出來而已。
replace:其實就是search操作之后,把start index和end index之間的字符串換成另外的而已。
所以,歸根到底,都是一個search操作的拓展罷了。這么一想,感覺清晰多了。
這么一來,API對search的能力支持的好壞和效率高低是衡量字符串操作功能的標(biāo)準(zhǔn),當(dāng)然,如果有直接支持match,split,replace操作的話就更好了。
java對字符串search的支持,最基本的就是下面的String的indexOf方法:
int indexOf(String str)
????????? Returns the index within this string of the first occurrence of the specified substring.
這里我想說的是,很多時候我們所謂要search的目標(biāo)串,根本就不是固定單一的,而是變化多樣的。如果只有一兩種情況,最多用兩次上面的方法唄。但是有些情況是近乎不可能羅列的,例如,我們講的代表email的字符串,我們不可能遍歷它吧。
所以,需要一種能夠通用表達字符串格式的語言。這就是Regular Expression(re)。
假如上面方法indexOf的str參數(shù)能支持re做為參數(shù)的話,那對于這種多樣的search也可以用上面的方法了。
可惜,indexOf不支持re作為參數(shù)。
so,以下就介紹java api中可以用re作為參數(shù)的字符串操作方法(參數(shù)中的regex就是re)。
--------------------->>
String類的:
全match操作:
boolean matches(String regex)
????????? Tells whether or not this string matches the given regular expression.
全replace操作:
String replaceAll(String regex, String replacement)
????????? Replaces each substring of this string that matches the given regular expression with the given replacement.
首個replace操作:
String replaceFirst(String regex, String replacement)
????????? Replaces the first substring of this string that matches the given regular expression with the given replacement.
全split操作:
String[] split(String regex)
????????? Splits this string around matches of the given regular expression.
有限制數(shù)的split操作:
String[] split(String regex, int limit)
????????? Splits this string around matches of the given regular expression.
<<---------------------
可惜啊,可惜,可惜java的String類里面沒有可以支持re的search方法,那如果要用re來search,只好使用java中專門的re類庫。
java中的re類庫主要就兩個類,一個叫Pattern,顧名思義,代表re的類。一個叫Matcher類,反映當(dāng)前match狀況的類(如存放了當(dāng)前search到的位置,匹配的字符串等等信息)。
一般在構(gòu)造中,“re的表達式”作為參數(shù)傳遞入Pattern類,“輸入串(待過濾串)”作為參數(shù)傳遞入Matcher類。
然后使用Matcher類的字符串search方法就可以了。Matcher真正提供search功能的API叫find。下面列出。
--------------------->>
Matcher類search操作相關(guān)的方法:
boolean lookingAt()
????????? Attempts to match the input sequence, starting at the beginning, against the pattern.
boolean matches()
????????? Attempts to match the entire input sequence against the pattern.
boolean find()
????????? Attempts to find the next subsequence of the input sequence that matches the pattern.
String group()
????????? Returns the input subsequence matched by the previous match.
<<---------------------
前三個都是search方法,返回成功與否。第四個是返回當(dāng)前search上的字符串。
ok,至此。使用re的search操作也有眉目了。
當(dāng)然,Pattern和Matcher也包含直接使用re進行的match,split,replace操作。
--------------------->>
Patter類別的字符串操作方法
全match操作:
static boolean matches(String regex, CharSequence input)
????????? Compiles the given regular expression and attempts to match the given input against it.
全split操作:
String[] split(CharSequence input)
????????? Splits the given input sequence around matches of this pattern.
有限制數(shù)的split操作:
String[] split(CharSequence input, int limit)
????????? Splits the given input sequence around matches of this pattern.
Matcher類別的字符串操作方法
全replace操作:
String replaceAll(String replacement)
????????? Replaces every subsequence of the input sequence that matches the pattern with the given replacement string.
首個replace操作:
String replaceFirst(String replacement)
????????? Replaces the first subsequence of the input sequence that matches the pattern with the given replacement string.
動態(tài)replace(replacement可以根據(jù)被替代的字符串變化而變化)
Matcher appendReplacement(StringBuffer sb, String replacement)
????????? Implements a non-terminal append-and-replace step.
StringBuffer appendTail(StringBuffer sb)
????????? Implements a terminal append-and-replace step.
<<---------------------
總結(jié):
當(dāng)必須使用re的時候,search操作就要用到Pattern,Matcher,當(dāng)然動態(tài)的replace操作也要用到這兩個類。而別的match,replace,split操作,可以使用pattern,Matcher,當(dāng)然也可以直接使用String,推薦還是用回咱們的String吧。
注:以上都是看jdk1.4以上的文檔得出的結(jié)論,以前版本不能用不負責(zé)任。
創(chuàng)建和銷毀對象
重點關(guān)注對象的創(chuàng)建和銷毀:什么時候、如何創(chuàng)建對象,什么時候、什么條件下應(yīng)該避免創(chuàng)建對象,如何保證對象在合適的方式下被銷毀,如何在銷毀對象之前操作一些必須的清理行為。
嘗試用靜態(tài)工廠方法代替構(gòu)造器
如果一個 client 要實例化一個對象來使用,傻 b 都知道應(yīng)該先調(diào)用類的構(gòu)造器來 new 一個對象,之后再調(diào)用相應(yīng)的方法。除了這個方式, Java Effective 還建議了另一種方法:用靜態(tài)工廠方法來提供一個類的實例。以下的例子不反映兩者的優(yōu)劣,只是反映兩者在代碼實現(xiàn)上的不同,優(yōu)劣之后再談:
假設(shè)咱們要一個顏色為黑色、長度為
Hammer myHammer =? new Hammer(Color.BLACK, 50);
而用靜態(tài)工廠方法來實例化一個對象,如下
Hammer myHammer = Hammer.factory(Color.BLACK,50);
也可以用專門的一個工廠類來實例化
Hammer myHammer = Toolkit.factory(“Hammer”, Color.BLACK,50);?
單純從上面的代碼上看,真的只有傻 b 才會選擇靜態(tài)工廠的方法,完全就是多此一舉,直接 new 又快又爽,搞這么麻煩做莫斯(武漢話“什么”的意思)?
別急,別急,你急個莫 b (武漢粗話:基本就是“你急個毛”的意思)?
下面就說說用靜態(tài)工廠代替構(gòu)造器的好處( advantage )和不好處( disadvantage )。
第一個好處,講你都不信,行家們認為,構(gòu)造器有一個不好的地方就是:這個方法的簽名(
signture
)太固定了。
構(gòu)造器的名字是固定的,生個 Hammer ,構(gòu)造器的名字就是 Hammer (……),唯一能變化的地方就是參數(shù),假設(shè)我的這個錘子有兩個很變態(tài)的構(gòu)造需要:
1 :第一個參數(shù)是顏色( Color 型),第二個參數(shù)是錘子頭的重量( int 型)。
Hammer ( Color c, int kg ) {
//remainder omited
}
2 :第一個參數(shù)是顏色( Color 型),第二個參數(shù)是錘子的長度( int 型)。
Hammer ( Color c, int cm ) {
//remainder omited
}
感覺滿足需要了,但是細心一看,完了,構(gòu)造器的參數(shù)列表類型重復(fù)了,肯定編譯通不過,這是面向?qū)ο髽?gòu)造器天生的缺陷——唯一的變化就是參數(shù),參數(shù)都分辨不了,就真的分辨不了。
而另外就算參數(shù)能分辨的了,構(gòu)造器一多,它的參數(shù)一多,您根本就不知道每個參數(shù)是用來干什么的,只能去查閱文檔,在您已經(jīng)眼花繚亂的時候再去查文檔,一個一個的對,折磨人的活。
這個時候,您就可以考慮用靜態(tài)工廠方法來實例化對象了。因為靜態(tài)工廠方法有一個最簡單的特點就是:他有可以變化的方法名(構(gòu)造器的名字變不了)。用名字的不同來代表不同的構(gòu)造需要,這么簡單的普通的特點在這里就是它相對于構(gòu)造器的 advantage 。
如上面的錘子的例子可以這樣:
1 : Hammer.produceByWeight (Color c, int kg){
//remainder omited
}
2 : Hammer.produceByHeight (Color c, int cm){
//remainder omited
}
這是不是一目了然多了。嗯,我是這樣認為的。
第二個好處,“靜態(tài)工廠方法不需要每次都真的去實例化一個對象”——其實這也是另一些優(yōu)化方法的前提。
構(gòu)造器的每次 invoke 必定會產(chǎn)生一個新的對象,而靜態(tài)工廠方法經(jīng)過一定的控制,完全可以不用每次 invoke 都生成一個新的對象。
為什么不每次都生成一個對象的原因就不必說了,因為原因太明顯。這個原因就是為什么要“共享”對象的原因。
下面講講通常使用的兩種共享具體策略,也就是具體方法了:
1 :單例模式的需要,一旦需要某個對象有單例的需要,必定對于這類對象的構(gòu)造只能用靜態(tài)工廠方法了。
2 : flyweight 模式和不變( immutable ) 模式的需要,這兩個模式很多時候都說一起使用的,一旦一些對象我們認為是不變的,那自然就想拿來重用,也就說共享,而 flyweight 就是用來重用這些小粒度對象的。
如 Boolean.valueOf (boolean) 方法:
Boolean a = Boolean.valueOf (100);
Boolean b = Boolean.valueOf (100);
?a,??b兩個引用都是指向同一個對象。
這些對象都是不變的,而 valueOf 的控制就是用的 flyweight 方法。
這種一個狀態(tài)(如上面一個數(shù)字)對應(yīng)的對象只有一個還有一個好處,就是可以直接通過比較“引用”來判斷他們是否
equel
(這里的
equel
是邏輯相等的意思),以前需要
a.equels(b)
,而一旦用“
flyweight
模式和不變(
immutable
)
模式”后,避免了產(chǎn)生多余的相同對象,用
a==b
就可以達到
a.equels(b)
的目的了。這樣當(dāng)然優(yōu)化了
performance
。?
第三個好處,其實就是工廠方法的核心好處——我把它稱為“抽象類型構(gòu)造器”。它可以為我們提供一個抽象類型的實例,同時必要的隱藏了抽象類型的具體結(jié)構(gòu)。這是
new
怎么都達不到的。
這種模式的好處其實就是面向?qū)ο蟮淖詈诵牡暮锰帲橄蠛途唧w可以分離,一旦抽象定義好了,具體的東西可以慢慢的變化,慢慢的拓展——開閉原則。
如
Collections Framework API
,都是描述集合類型的接口,也就是對于客戶端來看,只有
Collection
這個類要認識,而實際上,實現(xiàn)這個接口的
Collection
是多種多樣的。如果要讓用戶都知道這些具體實現(xiàn)的
Collection
,就增加了復(fù)雜度。
這時,通過一個靜態(tài)工廠方法,就可以隱藏各種
Collection
的具體實現(xiàn),而讓
Client
只使用返回的
Collection
對象就可以了。
這里還可以加上一些權(quán)限控制,如這些實現(xiàn)只要對于工廠來講是可以訪問的,不用是
public
的,而他們只要通過
public
的工廠就可以提供給用戶。非常有利于代碼的安全。
靜態(tài)工廠方法的第一個缺點就是:使用靜態(tài)工廠方法創(chuàng)建的類的構(gòu)造器經(jīng)常都是非公共或非
protected
的。
這樣,以后這些類就沒有辦法被繼承了。不過也有人說,不用繼承就用
composition
唄。也是!呵呵。
靜態(tài)工廠方法的第二個缺點是:在
jdk
文檔里,這些靜態(tài)工廠方法很難跟別的靜態(tài)方法相區(qū)別。
而文檔中,構(gòu)造器是很容易看到的。
為了一定程度解決這個問題,我們可以用一些比較特別的名字來給這類靜態(tài)工廠方法來命名。最常用的有:
valueOf
——
用來放回跟參數(shù)“相同值”的對象。
getInstance
——
返回一個對象的實例。單例模式中,就是返回單例對象。
總結(jié):靜態(tài)工廠方法和構(gòu)造器都有各自的特點。最好在考慮用構(gòu)造器之前能先考慮一下靜態(tài)工廠方法,往往,后者更有用一點。如果權(quán)衡了以后也看不出那個好用一些,那就用構(gòu)造器,畢竟簡單本分多了。
這幾天瞄了幾本設(shè)計模式的書,沒有細看具體模式啦,而是老是琢磨那些深奧無比的話。這些話經(jīng)常出現(xiàn)在計算機的書籍中,很有禪意,也有哲理。聽說,高手就喜歡寫點這樣的話。
還有就是細心體味了一下OO的設(shè)計原則,這些原則是凌駕于模式之上的,也就是更宏觀的原則。
其中,最高指導(dǎo)的一個就是“開-閉”原則。別的原則,里氏代換原則、依賴倒置原則、組合/聚合復(fù)用原則和迪米特法則都是為了達到“開-閉”原則而出現(xiàn)的規(guī)則。
這些原則告訴我很多東西,聚焦于一點就是要“面向抽象”來做一切事情。
分析對象的時候,要多分析設(shè)計“抽象”的概念,對象之間的聯(lián)系要多基于抽象的概念而不是具體,這樣具體才能能夠變化,這樣才是開閉。用我自己的話就是要“游走于 抽象”。
這里有一個我必須記住的就是,在封裝變化時候,多用聚合/組合,少用繼承。在封裝原子變化并且是同類型對象時才用繼承,別的都盡量用聚合/組合。而且盡量不要用多級繼承,多級繼承一般意味著有兩種變化脈絡(luò),可能的話,讓兩種變化脈絡(luò)獨立演化。很明顯,一獨立演化,又要聚合/組合了。
還有一個必須記住的是:運用抽象以后,客戶端的使用發(fā)生了巨大的變化。不再是指那兒用那兒。而是要做更多的準(zhǔn)備工作,因為運用抽象,本身就把具體“組合”的職責(zé)推遲到使用的階段。那誰使用,肯定是客戶端。所以,客戶端的使用要革新。要習(xí)慣用工廠,習(xí)慣把一系列的抽象定具體了,并按照一定方式“組合”起來用。而且,最終要善于用接口來調(diào)用方法。
用小飛推薦的一個工具畫了個圖,如下:
MARCO ZHANG 2006年2月27日7:18:57
--“design with the think of sharing objects”,F(xiàn)lyweight模式應(yīng)用
“共享”的思想
共享的idea是生活中最基本的idea,不必有意的使用,到處已經(jīng)存在了。在生活中,大部分事物都是被多人多次使用的,這都是共享的實際應(yīng)用。
之于OO的共享
OO中的共享,無非就是說讓“對象”也能被“多人多次”(這里的“人”也無非就是進程、線程而已)使用,更詳細的說,就是要讓對象的生存空間更大一些,生存周期更長一些。
自己悶個兒腦子,提煉出了幾個需要使用共享的環(huán)境(context),也可以說是原因吧:
1. 為了保持“對象”的一致,我們需要共享。例如,“國家主席”就一個,不能多了,如果多了,難免決策混亂。
2. 為了控制“對象”的存儲空間,我們需要共享。畢竟,目前來說,系統(tǒng)的memory還是編程時最珍貴的資源。
3. 為了優(yōu)化“對象”的創(chuàng)建消耗,我們需要共享。如果,一個對象的創(chuàng)建過程消耗太大,系統(tǒng)不能支持頻繁的創(chuàng)建,共享的使用它也是一個好主意。
4. 等等。
而在實際的應(yīng)用中,往往我并沒有細想“我為什么使用共享?”,已經(jīng)不自覺的就用了;如果真的認真分析起來,基于的環(huán)境也是多樣,并不會只是上面的其中一種。
常用的“共享”方法或模式(我曾經(jīng)用過的,知道的不多,望諒解):
1. “Singleton模式”:一個class就一個對象實例,大家都用它,滿足context1。
2. “pool技術(shù)”:只提供一定數(shù)目的對象,大家都用他們,實現(xiàn)context2、context3。
3. “flyweight模式”:一個class的一個狀態(tài)就一個對象實例,實現(xiàn)一個狀態(tài)對象的共享,實現(xiàn)context2、context3。
使用時要注意的地方:
1. 確定共享的scope。例如,在Java Web Application中就是選擇是page,session還是application,當(dāng)然也可以是jvm級別的static。
2. 確認thread safe。當(dāng)共享的對象可能被多個線程共享時,這是不可以回避的問題。
3. 應(yīng)對對象狀態(tài)的變化。一旦共享的對象發(fā)生了變化,我們怎么處理?改變之,舍棄之?也是我們需要確定的。
項目中的應(yīng)用:
項目需求:
為學(xué)校的同學(xué)提供Web查詢,查詢的內(nèi)容有很多。其中,“查課表”、“查考表”是最為關(guān)鍵的需求,以后可能還要提供“查詢空閑自習(xí)教室”的功能。
在這些查詢中,有一個共同點,就是都涉及“教室”這一對象。“查課表”時要告訴同學(xué)在哪個教室上課,“查考表”時要告訴同學(xué)在哪個教室考試,等等。
數(shù)據(jù)庫設(shè)計:
對于“查課表”用例,有關(guān)的數(shù)據(jù)庫設(shè)計如下:
對象層的設(shè)計:
學(xué)生每查詢一門課程的課表,系統(tǒng)就會sql查詢“視圖V_LESSONSCHEDULE”,進而生成一個LessonSchedule對象,然后返回給用戶顯示。當(dāng)然,在生成這個LessonSchedule對象的過程中,屬于它的Classroom對象,以及更深一步的Building、Area對象都會生成。下面就是這個過程的順序圖:
因此,每生成一個“課表”對象(LessonSchedule)或“考表”對象(ExamSchedule)時,都要:
1. 查數(shù)據(jù)庫中的教室、教學(xué)樓、校區(qū)的信息;
2. 創(chuàng)建相應(yīng)的“教室對象”(包括了屬于它的“教學(xué)樓”對象和“校區(qū)”對象)。
考慮共享“教室”對象
“教室”對象一旦可以生成以后,完全可以給后續(xù)共享使用,不必要每個查詢都要去生成。
詳細說是基于下面的考慮:
1. 這類查詢用例(查課表,查考表)發(fā)生的頻繁很高很高,也就是說,一旦讓用戶查起來,系統(tǒng)中會產(chǎn)生大量的“教室”對象這類對象,應(yīng)該說會占很大的內(nèi)存空間。
2. 共享“教室”對象后,可以減少對數(shù)據(jù)庫的查詢次數(shù),并降低了查詢粒度(以前是基于二級視圖查詢,現(xiàn)在可以基于基本表查詢),提高了一點數(shù)據(jù)庫查詢性能。
當(dāng)然,同時我腦袋中也有反對的聲音:
1. 雖說,這類查詢會產(chǎn)生很多相同的“教室”對象,但是JVM本生提供的垃圾回收功能完全可以處理它。除非,“同時”有很多很多這類對象都在被使用,根本回收不了,才會造成內(nèi)存短缺。
2. 如果,我以某種共享機制讓這些“教室”對象,在系統(tǒng)中存在下來(延長了生命周期)了。而它們本身的數(shù)目就很多(如,我們學(xué)校就有××),可能還沒有等你用上,系統(tǒng)已經(jīng)掛了。另外,如果不是同時有很多查詢需要,我留這么多“教室”對象在系統(tǒng)里,反而是占了內(nèi)存,而不是優(yōu)化了內(nèi)存的使用。
1. 所有模式的通病――“增加了復(fù)雜度”。
結(jié)論:
經(jīng)過我們分析,系統(tǒng)對于“教室”對象的重復(fù)使用,頻繁程度非常高。一般,有10個人同時使用,就有5個人左右涉及的用例要使用“教室”對象。把它共享起來,還是有一定必要的。
進一步考慮:
而實際上,我們可以進一步共享下去:
除了讓客戶端共享“教室對象(Classroom)”外,還可以讓“教室對象”共享“教學(xué)樓對象(Building)”,和讓“教學(xué)樓對象”共享“校區(qū)對象(Area)”。
因此,最終的共享是在三級上都實現(xiàn)。
Flyweight模式:
特點:
書上說:“享元模式可以使系統(tǒng)中的大量小粒度對象被共享使用”。第一,對象出現(xiàn)的量要大,想必這比較好理解,很少使用的也就沒有必要共享了;第二,要小粒度,我比較納悶?難道對于大粒度對象就不行嗎?可能書上認為,大粒度對象的共享已經(jīng)占了比較大的空間,沒有小對象那么有效吧。
另外,書上還說,要使用“享元模式”,被共享的對象的狀態(tài)(類別)要比較固定,這樣就可以為每一個狀態(tài)僅僅創(chuàng)建一個對象。當(dāng)然,如果每次使用對象時,對象的狀態(tài)都是不一樣的,那就根本不存在共享這些對象的必要了。
聯(lián)系項目思考:
基于上面對項目的分析,“教室”、“教學(xué)樓”、“校區(qū)”對象都是在系統(tǒng)中會被大量使用的對象,而且粒度的確比較小;并且它們有固定的類別,而且不易改變。如校區(qū)對象,暫時就有4個。教學(xué)樓可能40-50個左右。很適合“享元模式”的使用環(huán)境。
確定共享方式:
1. 確定共享對象的scope。在本web程序中,這些共享對象的scope理應(yīng)是application,而更簡單的一個作法就是把這些對象設(shè)為static,我選擇后者。
2. 確認thread safe。這些對象是可能被多個servlet訪問的,也就是有可能存在多線程訪問。但是,由于這些對象的可變性很差,一旦創(chuàng)建就不大可能變化。因此,我決定把這寫共享對象設(shè)計成不變模式的,一旦創(chuàng)建就只會被讀取,而不會改寫,這樣就不存在多線程控制的問題了。
3. 應(yīng)對對象狀態(tài)的變化,如某個教室的類型變了。這里采取的是舍棄的方法,為每個工廠添加了一個清空方法――clear(),用于清空已經(jīng)生成的共享對象。
設(shè)計類圖:
當(dāng)然,也可以把這些工廠都設(shè)計成“Singleton模式”的,使它們只會有一個實例。
客戶端使用:
由于共享的對象都被包含在了“課表”和“考表”對象里,不會被客戶端直接訪問,因而不會對客戶端的使用有任何影響:
實例代碼

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

20

21

22

23

24

25

26

27

28

29

30

31

32

33

34

35

36

37

38

39

40

41

42

43

44

45

46

oneClassroom與twoClassroom;oneBuilding與twoBuilding;oneArea與twoArea由于都是32號課程的東西,根據(jù)我們的設(shè)計意圖,應(yīng)該實現(xiàn)共享。
而實際上,它們每對的確是同一個對象的引用。因此,實現(xiàn)了預(yù)期的設(shè)想。
Review:
在本項目中,當(dāng)?shù)谝淮卧O(shè)計出來的時候,我們發(fā)現(xiàn)了某些對象恰好有共享的需要。
而更多的實際情況是,這些需要共享的“信息或狀態(tài)”在設(shè)計中并不是那么恰好的表現(xiàn)為“一個對象”的粒度,而是要不就包含在一個對象內(nèi)部,要不就跨幾個對象。在這樣的情況下,共享的設(shè)計更多是發(fā)生在代碼重構(gòu)階段而不是第一的設(shè)計階段。當(dāng)然,為了共享對象而做出的代碼重構(gòu),最重要的一步就是把需要共享的“信息或狀態(tài)”設(shè)計成為新的對象。
對于,“享元模式”來說,就是要把需要共享的“信息或狀態(tài)”設(shè)計成“享元對象”。別的在此就不說了,因為我也不懂了,呵呵。
MARCO ZHANG 2006年2月23日13:48:49
廢話:
預(yù)料中的日志3暫時生產(chǎn)不出來,話說難產(chǎn)就好,別夭折就行,有點掉價哦,呵呵。
因為有些東西還沒有想清楚。那就先搞個四吧,這個東西還是清楚那么一點的。
一版描述:
項目需求 |
使每個servlet能對用戶訪問權(quán)限進行檢查。簡單來說,就是要給每個servlet加個鎖,有鑰匙的用戶才能訪問。 |
而項目中用戶所謂的訪問權(quán)限是基于他擁有的角色。也就是說,servlet對用戶訪問權(quán)限的檢查,就是對他所擁有角色的檢查。暫時,每個用戶只能擁有一個角色。
項目的角色很多,但是在web端暫時只有如下的三種:
項目暫時角色 |
游客,學(xué)生,教師 |
既然這樣,servlet的加鎖方式就有:
servlet加鎖方式 |
游客,學(xué)生,教師, |
注:上面的“游客”就是“游客角色有權(quán)訪問”的意思,依此類推。
這里只有關(guān)系“或”(||),如果一個servlet的加鎖方式是“學(xué)生或教師”,也就是說擁有學(xué)生或教師角色的用戶都可以訪問它。關(guān)系“與”(&&)在這里不太可能存在,因為沒有需求說:某個servlet一定只能由“既是學(xué)生又是教師的用戶”才能訪問,而且前面也說了,暫時一個用戶“有且只有”一個角色。
So we can get following function:
|
運用OO的最基本方式,就是封裝對象。既然有為servlet“看門”的責(zé)任,那就把這個責(zé)任封裝成一個對象,用個俗名:validator。
接著,運用共性和個性的分析方法,既然有那么多種不同的看門方式(加鎖方式),那就搞一個接口,然后讓各種“不同”都實現(xiàn)這個接口,形成子類。那就有下面的圖:
可以看到,由于有7個加鎖方式,那就要有7個子類。每個子類根據(jù)自己邏輯override接口的validate方法。
這樣,對于一個servlet,想讓它上什么樣的鎖,只要讓它拿到對應(yīng)的子類的引用即可,如下圖中的ClientServlet,我們規(guī)定只能有“學(xué)生或教師”才能訪問它。它的部分代碼便是:

2

3

4

5

至此,第一個解決方案就出來了。
思考:
不足 |
validator接口的子類數(shù)目隨“角色數(shù)”成“指數(shù)級”增長,數(shù)量太多;而且子類中重復(fù)邏輯的代碼很多,如“Student_Or_Teacher_Validator”就重復(fù)了“Student_Validator”和“Teacher_Validator”的邏輯,萬一“Student_Validator”的邏輯要改,只要涉及Student的子類都要跟著改,維護上不方便。 |
進一步改進的可能 |
“Student_Or_Teacher_Validator類”的validate方法很大程度上就是“Student_Validator類”的validate方法和“Teacher_Validator類”的validate方法“或操作”出來的結(jié)果。 因此,能不能考慮由“Student_Validator類的validate方法”和“Teacher_Validator的validate方法”動態(tài)的構(gòu)造一個功能如“Student_Or_Teacher_Validator類的validate方法”。 這樣,“Student_Or_Teacher_Validator”就可以省略了,只剩下一些原子的“角色類”。要實現(xiàn)Student_Or_Teacher_Validator的驗證功能,拿Student_Validator和Teacher_Validator裝配一下就可以了。 |
結(jié)論,需要根據(jù)實際情況,動態(tài)的改變(裝配)“Validator接口對象”的validate方法。
第一個火花就是“裝飾模式”。它可以讓客戶端動態(tài)的組裝對象的方法。真神奇!
第二版來啦
注:上圖中出現(xiàn)了AndRelation和And的一系列角色類,可以暫時省略不看,因為前面說了,現(xiàn)在還沒有“與”關(guān)系這個需求。只看Or的就可以。
我喜歡叫這個模式為洋蔥模式,一層包一層,最外層對象某方法的邏輯是由內(nèi)部一層一層對象的同一方法組合出來的。
使用了這個模式,便不用如一版那樣實現(xiàn)那么多子類,只要實現(xiàn)幾個“角色類”即可,這里有3個(學(xué)生角色:Or_Student、教師角色:Or_Teacher、游客角色:Or_Guest)。所有一版中別的子類都可以由這3個組裝出來。
如要生成一版中的Student_Or_Teacher_Validator對象,可以用Or_Student和Or_Teacher兩個對象“Or”出來:

如要生成一版中的Guest_Or_Student_Or_Teacher_Validator對象,可以用Or_Student、Or_Teacher和Or _Guest三個對象“Or”出來:

這種一層包一層的new方式,是不是很像洋蔥?第一次看是很不習(xí)慣,看多了就覺得習(xí)慣了。
對客戶端的影響:
一版中,客戶端要什么樣的驗證類,就直接使用具體類。
二版中,客戶端要什么樣的驗證類,它的工作多了那么一丁點,它需要先組裝一下,正如上面的例子。這種組裝的方法很易于理解和使用,不會給客戶端帶來任何的不便。如果實在覺得客戶端組裝不出來(傻B客戶端),也可以搞個工廠給它supply!
優(yōu)點:
相對一版最明顯的優(yōu)點就是類的數(shù)目少了很多。
一版不是說“指數(shù)級”嗎?這里只是線性的了。假設(shè)某一天系統(tǒng)拓展到有10個角色,一版就有2的10次方那么多個,也就是1024個驗證類。
而二版還是10個角色類,別的都可以在客戶端使用的時候,動態(tài)的組裝
更重要的是代碼結(jié)構(gòu)好了,重復(fù)邏輯少了。每個邏輯都以最atomic的大小放到最應(yīng)該的地方。
進而,維護的代價少多了。如某天“教師角色”的驗證邏輯發(fā)生了變化,只要改動Or_Teacher一個地方即可。
MARCO ZHANG 2006年2月18日23:49:56
說起這個工廠模式,一時還真不知道該如何說起。反正這是我的開發(fā)日志,不提理論的東西,理論的東西那里都有,我只想把具體實踐記錄下來給師弟師妹們作個參考,積累點經(jīng)驗。所有這些文字都是集中講一點――“在什么情況下為什么用某種模式好,為什么好,為什么在那種情況下能想起來用?”。
研究生院項目中“明顯”使用了“工廠方法模式”。其實在遇到具體問題的時候,即使我們不知道有這個模式存在,我們也肯定會造一個類似的東西出來。但是,肯定沒有書上論述的那么好,那么全面。我想這就是看書的好處吧。
工廠方法出現(xiàn)的必然(我的理解,一個很狹隘并幼稚理的人的理解)
剛開始使用這個東西的時候,只是感覺是單純的一種模式,用于創(chuàng)建需要的對象。
但是隨著使用和思考的深入,越發(fā)發(fā)現(xiàn)它給我的啟示不只在于單純的對象創(chuàng)建,而是告訴我應(yīng)該怎么理解“產(chǎn)品”,怎么得到“產(chǎn)品”,怎么消費“產(chǎn)品”,以至于以后怎么設(shè)計“產(chǎn)品”。
下面這個線索是我對它出現(xiàn)必然性的理解:
1. “針對接口編程”
這是OO世界中經(jīng)典的規(guī)范,不管你主動還是被動,你天天都在用這個東西。
接口是共性的表示,在對象的世界中,共性和個性的辯證關(guān)系是最重要的關(guān)系。在萬千的對象中,通過它們之間的共性和個性,可以形成最基本對象層級架構(gòu)。
假設(shè)我們的討論域中有以下一些對象:“學(xué)生”、“大學(xué)生”、“小學(xué)生”、“中學(xué)生”;我們不用細想,學(xué)過一天OO的人都可以為這些耳熟能詳?shù)膶ο髠儯ㄟ^個性和共性的關(guān)系得出下面的結(jié)構(gòu)圖。
把這些對象之間的關(guān)系定義成這樣是順理成章的。
下一步肯定是讓客戶端“使用”這個接口啦。也就是如下圖:
2. 接口和具體類的矛盾
勿庸置疑,我們只希望Client跟接口Student打交道,讓它根本就不知道Student有哪些子類,絕對不希望直接跟它們打交道。
但這里出現(xiàn)的困難是,接口都是“假”的,都是由具體類upcast的。
如果Client要使用接口Student,Client中必須會出現(xiàn)下面的代碼:
Student marco = new Small_Student();
只要一出現(xiàn)這個代碼,就說明Client不只跟Student打交道了,它知道了Small_Student類,這違反了我們預(yù)先的想法。
3. 找“人”幫我去創(chuàng)建“接口對象”
從上圖體現(xiàn)出來的結(jié)構(gòu)看,Client只想跟Student打交道的目的是實現(xiàn)不了的了。
最簡單的方法就是找另外的“幫手”去幫我生成這個“接口對象”。這個幫手它知道“接口對象”的具體類型,但是它為客戶端提供的卻一定是“接口類型”。這就符合我們的要求了。如圖:
這樣,Client就可以既用到了“Student接口對象”,又不用因為“只有具體類才能創(chuàng)建對象”的規(guī)則,而必須對其子類結(jié)構(gòu)有完全的了解。它成功的解決了2中的矛盾。
而“負責(zé)創(chuàng)建具體類對象的任務(wù)”全部都落在了這個“幫手”身上,這個“幫手”(Student_Factory)就是工廠模式中的工廠類,更具體的說,它就是“簡單工廠模式”中的“簡單工廠類”。
我覺得,即使一點都不知道工廠模式,一旦我遇到了2里說的矛盾,我也會用這樣的方法處理。
4. 這個“幫手”不符合“開-閉原則”
這個幫手的確不錯了,而且一躍成為系統(tǒng)中最重要的對象了,所有“創(chuàng)建具體類的邏輯”都放進去了,也就是因為重要,萬一掛了不就慘了。
再者,它不符合“開-閉”原則,我不能在不修改這個幫手的情況下添加任何一個產(chǎn)品。在這個例子中就是,如果那天我有病非要加一個“幼兒園學(xué)生”進來,那您就必須修改這個“幫手”的代碼了,這個“幫手”現(xiàn)在就變成Version2(如下圖)了,這個二版的幫手,可以在“代碼”層實現(xiàn)對一版(還沒有添加幼兒園學(xué)生之前)的通用,但這種保證在“開-閉”原則看來,還是不夠的,不保險的,它要的是在類的結(jié)構(gòu)上的保證。聲明一下,這是我很感性的理解,不正確的可能性很高。
5. 讓“幫手”也多態(tài)
這里可以嘗試讓“幫手”也多態(tài)一下,這樣“每種學(xué)生類創(chuàng)建的任務(wù)”都被分派到了多態(tài)出來的類中去了。這時,再有新的學(xué)生類型加進來,添加一個對應(yīng)的幫手就可以了。這樣雖然類多了一些,但是符合“開-閉”原則,書上稱之為“工廠方法模式”。如圖:
假如Client現(xiàn)在要使用一個小學(xué)生,代碼如下:

2

3

4

5

6

7

在這里還是要強調(diào)兩點:
n 雖然實際上Client的確使用了一個小學(xué)生對象(Small_Student),但這里Client也認為它就是Student對象,這里一定要用Student接口來隱藏它的具體類。
n 另外,卻不需要用Student_Factory這個接口來隱藏它的具體類,因為,Client實際就是通過“選擇它的具體類”這招兒來“選擇創(chuàng)建的學(xué)生類型”。這里的Student_Factory更多的功能不是“隱藏”具體類,而是“規(guī)范”具體類。
項目實踐
扯淡到此,該聯(lián)系我們的項目啦。
由于是做研究生院的項目,其中巨大的需求就是要讓同學(xué)能在網(wǎng)上提交各種申請單,申請退學(xué)的,申請轉(zhuǎn)專業(yè)的,申請復(fù)學(xué)的,申請保留學(xué)籍的,除了申請女朋友的外,應(yīng)有盡有。 對于這些單子,用最最基本OO思維,根據(jù)共性個性分析方式,抽象出一個申請單接口,和若干的具體類。
當(dāng)然,除了概念上感性上吻合以外,在項目中它們也要“真”的有巨大的共性才行,如都要提交,修改,刪除,審核,打印等功能。
靠,既然都這樣了,肯定就用一接口規(guī)定它了。
想到這里,加上有了上面的思考,不用說了,就用工廠方法模式啦。
圖中Ent_Shift就是申請單接口。等于前面分析的Student接口。還可以看到有很多具體類,前面例子中是代表各種學(xué)生,這里就是代表各種申請單。
當(dāng)然,這里還有很多工廠,也就是前面一直叫的“幫手”。
Bean_Shift就是工廠接口,相當(dāng)于前面的Student_Factory接口。還有很多的具體類就是生產(chǎn)各種申請單的工廠類。
下面就是使用“申請單工廠方法模式”的一段客戶端代碼:

2

3

4

5

6



7

8

9

10

11

12

13

14

15

16

17

18

19

20

21



22

23

24

25



26

27

28

29

30

31

32

33

34

35

36

升華
個人比較同意《Design Pattern Explained》中作者講的,要用好很多的模式,其中都有一個思路,就是用接口或抽象類來隱藏子類的不同。
我每當(dāng)看到這時,老是會被一種思路困擾――“new只能new具體類啊,這咋能隱藏呢,這隱藏還有什么用呢?”。
作者仿佛也曾經(jīng)有過我的這個傻B苦惱,它的解決方法就是:根本在使用對象的時候,特別是設(shè)計階段,盡量不去想對象是在那里被new的。他認為:反正有了工廠模式后,你總有辦法把他們new出來的。
所以,我用了工廠模式后更發(fā)的啟發(fā)是:以后設(shè)計的時候不要想一個Client怎么創(chuàng)建一個對象,盡管放心大膽的先繼續(xù)想,直接使用就好了。反正最后我還有工廠模式呢。
因此俺的副標(biāo)題才是“Ignore how they were created”,呵呵。
MARCO ZHANG 2006年2月16日3:52:10