“共享”的思想
共享的idea是生活中最基本的idea,不必有意的使用,到處已經存在了。在生活中,大部分事物都是被多人多次使用的,這都是共享的實際應用。
之于OO的共享
OO中的共享,無非就是說讓“對象”也能被“多人多次”(這里的“人”也無非就是進程、線程而已)使用,更詳細的說,就是要讓對象的生存空間更大一些,生存周期更長一些。
自己悶個兒腦子,提煉出了幾個需要使用共享的環境(context),也可以說是原因吧:
1. 為了保持“對象”的一致,我們需要共享。例如,“國家主席”就一個,不能多了,如果多了,難免決策混亂。
2. 為了控制“對象”的存儲空間,我們需要共享。畢竟,目前來說,系統的memory還是編程時最珍貴的資源。
3. 為了優化“對象”的創建消耗,我們需要共享。如果,一個對象的創建過程消耗太大,系統不能支持頻繁的創建,共享的使用它也是一個好主意。
4. 等等。
而在實際的應用中,往往我并沒有細想“我為什么使用共享?”,已經不自覺的就用了;如果真的認真分析起來,基于的環境也是多樣,并不會只是上面的其中一種。
常用的“共享”方法或模式(我曾經用過的,知道的不多,望諒解):
1. “Singleton模式”:一個class就一個對象實例,大家都用它,滿足context1。
2. “pool技術”:只提供一定數目的對象,大家都用他們,實現context2、context3。
3. “flyweight模式”:一個class的一個狀態就一個對象實例,實現一個狀態對象的共享,實現context2、context3。
使用時要注意的地方:
1. 確定共享的scope。例如,在Java Web Application中就是選擇是page,session還是application,當然也可以是jvm級別的static。
2. 確認thread safe。當共享的對象可能被多個線程共享時,這是不可以回避的問題。
3. 應對對象狀態的變化。一旦共享的對象發生了變化,我們怎么處理?改變之,舍棄之?也是我們需要確定的。
項目中的應用:
項目需求:
為學校的同學提供Web查詢,查詢的內容有很多。其中,“查課表”、“查考表”是最為關鍵的需求,以后可能還要提供“查詢空閑自習教室”的功能。
在這些查詢中,有一個共同點,就是都涉及“教室”這一對象。“查課表”時要告訴同學在哪個教室上課,“查考表”時要告訴同學在哪個教室考試,等等。
數據庫設計:
對于“查課表”用例,有關的數據庫設計如下:
對象層的設計:

學生每查詢一門課程的課表,系統就會sql查詢“視圖V_LESSONSCHEDULE”,進而生成一個LessonSchedule對象,然后返回給用戶顯示。當然,在生成這個LessonSchedule對象的過程中,屬于它的Classroom對象,以及更深一步的Building、Area對象都會生成。下面就是這個過程的順序圖:
因此,每生成一個“課表”對象(LessonSchedule)或“考表”對象(ExamSchedule)時,都要:
1. 查數據庫中的教室、教學樓、校區的信息;
2. 創建相應的“教室對象”(包括了屬于它的“教學樓”對象和“校區”對象)。
考慮共享“教室”對象
“教室”對象一旦可以生成以后,完全可以給后續共享使用,不必要每個查詢都要去生成。
詳細說是基于下面的考慮:
1. 這類查詢用例(查課表,查考表)發生的頻繁很高很高,也就是說,一旦讓用戶查起來,系統中會產生大量的“教室”對象這類對象,應該說會占很大的內存空間。
2. 共享“教室”對象后,可以減少對數據庫的查詢次數,并降低了查詢粒度(以前是基于二級視圖查詢,現在可以基于基本表查詢),提高了一點數據庫查詢性能。
當然,同時我腦袋中也有反對的聲音:
1. 雖說,這類查詢會產生很多相同的“教室”對象,但是JVM本生提供的垃圾回收功能完全可以處理它。除非,“同時”有很多很多這類對象都在被使用,根本回收不了,才會造成內存短缺。
2. 如果,我以某種共享機制讓這些“教室”對象,在系統中存在下來(延長了生命周期)了。而它們本身的數目就很多(如,我們學校就有××),可能還沒有等你用上,系統已經掛了。另外,如果不是同時有很多查詢需要,我留這么多“教室”對象在系統里,反而是占了內存,而不是優化了內存的使用。
1. 所有模式的通病――“增加了復雜度”。
結論:
經過我們分析,系統對于“教室”對象的重復使用,頻繁程度非常高。一般,有10個人同時使用,就有5個人左右涉及的用例要使用“教室”對象。把它共享起來,還是有一定必要的。
進一步考慮:
而實際上,我們可以進一步共享下去:
除了讓客戶端共享“教室對象(Classroom)”外,還可以讓“教室對象”共享“教學樓對象(Building)”,和讓“教學樓對象”共享“校區對象(Area)”。
因此,最終的共享是在三級上都實現。
Flyweight模式:
特點:
書上說:“享元模式可以使系統中的大量小粒度對象被共享使用”。第一,對象出現的量要大,想必這比較好理解,很少使用的也就沒有必要共享了;第二,要小粒度,我比較納悶?難道對于大粒度對象就不行嗎?可能書上認為,大粒度對象的共享已經占了比較大的空間,沒有小對象那么有效吧。
另外,書上還說,要使用“享元模式”,被共享的對象的狀態(類別)要比較固定,這樣就可以為每一個狀態僅僅創建一個對象。當然,如果每次使用對象時,對象的狀態都是不一樣的,那就根本不存在共享這些對象的必要了。
聯系項目思考:
基于上面對項目的分析,“教室”、“教學樓”、“校區”對象都是在系統中會被大量使用的對象,而且粒度的確比較小;并且它們有固定的類別,而且不易改變。如校區對象,暫時就有4個。教學樓可能40-50個左右。很適合“享元模式”的使用環境。
確定共享方式:
1. 確定共享對象的scope。在本web程序中,這些共享對象的scope理應是application,而更簡單的一個作法就是把這些對象設為static,我選擇后者。
2. 確認thread safe。這些對象是可能被多個servlet訪問的,也就是有可能存在多線程訪問。但是,由于這些對象的可變性很差,一旦創建就不大可能變化。因此,我決定把這寫共享對象設計成不變模式的,一旦創建就只會被讀取,而不會改寫,這樣就不存在多線程控制的問題了。
3. 應對對象狀態的變化,如某個教室的類型變了。這里采取的是舍棄的方法,為每個工廠添加了一個清空方法――clear(),用于清空已經生成的共享對象。
設計類圖:
當然,也可以把這些工廠都設計成“Singleton模式”的,使它們只會有一個實例。
客戶端使用:
由于共享的對象都被包含在了“課表”和“考表”對象里,不會被客戶端直接訪問,因而不會對客戶端的使用有任何影響:
實例代碼
1
//取得編號為32號課程的“課表對象”
2
3
LessonSchedule oneLesson = LessonSchedule.findByLessonNum(32);
4
5
6
7
//獲得教室對象
8
9
Classroom oneClassroom = oneLesson.getLnkClassroom();
10
11
12
13
//獲得教學樓對象
14
15
Building oneBuilding = oneClassroom.getLnkBuilding();
16
17
18
19
//獲得校區對象
20
21
Area oneArea = oneBuilding. getLnkArea();
22
23
24
25
//再次重新生成一個編號為32號的“課表對象”
26
27
LessonSchedule twoLesson = LessonSchedule.findByLessonNum(32);
28
29
30
31
//獲得教室對象
32
33
Classroom twoClassroom = twoLesson.getLnkClassroom();
34
35
36
37
//獲得教學樓對象
38
39
Building twoBuilding = twoClassroom.getLnkBuilding();
40
41
42
43
//獲得校區對象
44
45
Area twoArea = twoBuilding. getLnkArea();
46
oneClassroom與twoClassroom;oneBuilding與twoBuilding;oneArea與twoArea由于都是32號課程的東西,根據我們的設計意圖,應該實現共享。
而實際上,它們每對的確是同一個對象的引用。因此,實現了預期的設想。
Review:
在本項目中,當第一次設計出來的時候,我們發現了某些對象恰好有共享的需要。
而更多的實際情況是,這些需要共享的“信息或狀態”在設計中并不是那么恰好的表現為“一個對象”的粒度,而是要不就包含在一個對象內部,要不就跨幾個對象。在這樣的情況下,共享的設計更多是發生在代碼重構階段而不是第一的設計階段。當然,為了共享對象而做出的代碼重構,最重要的一步就是把需要共享的“信息或狀態”設計成為新的對象。
對于,“享元模式”來說,就是要把需要共享的“信息或狀態”設計成“享元對象”。別的在此就不說了,因為我也不懂了,呵呵。
MARCO ZHANG 2006年2月23日13:48:49