作者:banq
理解并使用設(shè)計(jì)模式,能夠培養(yǎng)我們良好的面向?qū)ο缶幊塘?xí)慣,同時(shí)在實(shí)際應(yīng)用中,可以如魚(yú)得水,享受游刃有余的樂(lè)趣.
代理模式是比較有用途的一種模式,而且變種較多,應(yīng)用場(chǎng)合覆蓋從小結(jié)構(gòu)到整個(gè)系統(tǒng)的大結(jié)構(gòu),Proxy是代理的意思,我們也許有代理服務(wù)器等概念,代理概念可以解釋為:在出發(fā)點(diǎn)到目的地之間有一道中間層,意為代理.
設(shè)計(jì)模式中定義: 為其他對(duì)象提供一種代理以控制對(duì)這個(gè)對(duì)象的訪問(wèn).
為什么要使用Proxy?
1.授權(quán)機(jī)制 不同級(jí)別的用戶對(duì)同一對(duì)象擁有不同的訪問(wèn)權(quán)利,如Jive論壇系統(tǒng)中,就使用Proxy進(jìn)行授權(quán)機(jī)制控制,訪問(wèn)論壇有兩種人:注冊(cè)用戶和游客(未注冊(cè)用戶),Jive中就通過(guò)類似ForumProxy這樣的代理來(lái)控制這兩種用戶對(duì)論壇的訪問(wèn)權(quán)限.
2.某個(gè)客戶端不能直接操作到某個(gè)對(duì)象,但又必須和那個(gè)對(duì)象有所互動(dòng).
舉例兩個(gè)具體情況:
(1)如果那個(gè)對(duì)象是一個(gè)是很大的圖片,需要花費(fèi)很長(zhǎng)時(shí)間才能顯示出來(lái),那么當(dāng)這個(gè)圖片包含在文檔中時(shí),使用編輯器或?yàn)g覽器打開(kāi)這個(gè)文檔,打開(kāi)文檔必須很迅速,不能等待大圖片處理完成,這時(shí)需要做個(gè)圖片Proxy來(lái)代替真正的圖片.
(2)如果那個(gè)對(duì)象在Internet的某個(gè)遠(yuǎn)端服務(wù)器上,直接操作這個(gè)對(duì)象因?yàn)榫W(wǎng)絡(luò)速度原因可能比較慢,那我們可以先用Proxy來(lái)代替那個(gè)對(duì)象.
總之原則是,對(duì)于開(kāi)銷很大的對(duì)象,只有在使用它時(shí)才創(chuàng)建,這個(gè)原則可以為我們節(jié)省很多寶貴的Java內(nèi)存. 所以,有些人認(rèn)為Java耗費(fèi)資源內(nèi)存,我以為這和程序編制思路也有一定的關(guān)系.
如何使用Proxy?
以Jive論壇系統(tǒng)為例,訪問(wèn)論壇系統(tǒng)的用戶有多種類型:注冊(cè)普通用戶 論壇管理者 系統(tǒng)管理者 游客,注冊(cè)普通用戶才能發(fā)言;論壇管理者可以管理他被授權(quán)的論壇;系統(tǒng)管理者可以管理所有事務(wù)等,這些權(quán)限劃分和管理是使用Proxy完成的.
Forum是Jive的核心接口,在Forum中陳列了有關(guān)論壇操作的主要行為,如論壇名稱 論壇描述的獲取和修改,帖子發(fā)表刪除編輯等.
在ForumPermissions中定義了各種級(jí)別權(quán)限的用戶:
public class ForumPermissions implements Cacheable {
/** * Permission to read object. */ public static final int READ = 0; /** * Permission to administer the entire sytem. */ public static final int SYSTEM_ADMIN = 1; /** * Permission to administer a particular forum. */ public static final int FORUM_ADMIN = 2; /** * Permission to administer a particular user. */ public static final int USER_ADMIN = 3; /** * Permission to administer a particular group. */ public static final int GROUP_ADMIN = 4; /** * Permission to moderate threads. */ public static final int MODERATE_THREADS = 5; /** * Permission to create a new thread. */ public static final int CREATE_THREAD = 6; /** * Permission to create a new message. */ public static final int CREATE_MESSAGE = 7; /** * Permission to moderate messages. */ public static final int MODERATE_MESSAGES = 8; ..... public boolean isSystemOrForumAdmin() { return (values[FORUM_ADMIN] || values[SYSTEM_ADMIN]); } ..... } |
因此,Forum中各種操作權(quán)限是和ForumPermissions定義的用戶級(jí)別有關(guān)系的,作為接口Forum的實(shí)現(xiàn):ForumProxy正是將這種對(duì)應(yīng)關(guān)系聯(lián)系起來(lái).比如,修改Forum的名稱,只有論壇管理者或系統(tǒng)管理者可以修改,代碼如下:
public class ForumProxy implements Forum {
private ForumPermissions permissions; private Forum forum; this.authorization = authorization;
public ForumProxy(Forum forum, Authorization authorization, ForumPermissions permissions) { this.forum = forum; this.authorization = authorization; this.permissions = permissions; }
.....
public void setName(String name) throws UnauthorizedException, ForumAlreadyExistsException { //只有是系統(tǒng)或論壇管理者才可以修改名稱 if (permissions.isSystemOrForumAdmin()) { forum.setName(name); } else { throw new UnauthorizedException(); } }
...
}
|
而DbForum才是接口Forum的真正實(shí)現(xiàn),以修改論壇名稱為例:
public class DbForum implements Forum, Cacheable { ...
public void setName(String name) throws ForumAlreadyExistsException {
....
this.name = name; //這里真正將新名稱保存到數(shù)據(jù)庫(kù)中 saveToDb();
.... }
...
}
|
凡是涉及到對(duì)論壇名稱修改這一事件,其他程序都首先得和ForumProxy打交道,由ForumProxy決定是否有權(quán)限做某一樣事情,ForumProxy是個(gè)名副其實(shí)的"網(wǎng)關(guān)","安全代理系統(tǒng)".
在平時(shí)應(yīng)用中,無(wú)可避免總要涉及到系統(tǒng)的授權(quán)或安全體系,不管你有無(wú)意識(shí)的使用Proxy,實(shí)際你已經(jīng)在使用Proxy了.
我們繼續(xù)結(jié)合Jive談入深一點(diǎn),下面要涉及到工廠模式了,如果你不了解工廠模式,請(qǐng)看我的另外一篇文章:設(shè)計(jì)模式之Factory
我們已經(jīng)知道,使用Forum需要通過(guò)ForumProxy,Jive中創(chuàng)建一個(gè)Forum是使用Factory模式,有一個(gè)總的抽象類ForumFactory,在這個(gè)抽象類中,調(diào)用ForumFactory是通過(guò)getInstance()方法實(shí)現(xiàn),這里使用了Singleton(也是設(shè)計(jì)模式之一,由于介紹文章很多,我就不寫(xiě)了,看這里),getInstance()返回的是ForumFactoryProxy.
為什么不返回ForumFactory,而返回ForumFactory的實(shí)現(xiàn)ForumFactoryProxy?
原因是明顯的,需要通過(guò)代理確定是否有權(quán)限創(chuàng)建forum.
在ForumFactoryProxy中我們看到代碼如下:
public class ForumFactoryProxy extends ForumFactory {
protected ForumFactory factory; protected Authorization authorization; protected ForumPermissions permissions; public ForumFactoryProxy(Authorization authorization, ForumFactory factory, ForumPermissions permissions) { this.factory = factory; this.authorization = authorization; this.permissions = permissions; } public Forum createForum(String name, String description) throws UnauthorizedException, ForumAlreadyExistsException { //只有系統(tǒng)管理者才可以創(chuàng)建forum if (permissions.get(ForumPermissions.SYSTEM_ADMIN)) { Forum newForum = factory.createForum(name, description); return new ForumProxy(newForum, authorization, permissions); } else { throw new UnauthorizedException(); } }
|
方法createForum返回的也是ForumProxy, Proxy就象一道墻,其他程序只能和Proxy交互操作.
注意到這里有兩個(gè)Proxy:ForumProxy和ForumFactoryProxy. 代表兩個(gè)不同的職責(zé):使用Forum和創(chuàng)建Forum;
至于為什么將使用對(duì)象和創(chuàng)建對(duì)象分開(kāi),這也是為什么使用Factory模式的原因所在:是為了"封裝" "分派";換句話說(shuō),盡可能功能單一化,方便維護(hù)修改.
Jive論壇系統(tǒng)中其他如帖子的創(chuàng)建和使用,都是按照Forum這個(gè)思路而來(lái)的.
以上我們討論了如何使用Proxy進(jìn)行授權(quán)機(jī)制的訪問(wèn),Proxy還可以對(duì)用戶隱藏另外一種稱為copy-on-write的優(yōu)化方式.拷貝一個(gè)龐大而復(fù)雜的對(duì)象是一個(gè)開(kāi)銷很大的操作,如果拷貝過(guò)程中,沒(méi)有對(duì)原來(lái)的對(duì)象有所修改,那么這樣的拷貝開(kāi)銷就沒(méi)有必要.用代理延遲這一拷貝過(guò)程.
比如:我們有一個(gè)很大的Collection,具體如hashtable,有很多客戶端會(huì)并發(fā)同時(shí)訪問(wèn)它.其中一個(gè)特別的客戶端要進(jìn)行連續(xù)的數(shù)據(jù)獲取,此時(shí)要求其他客戶端不能再向hashtable中增加或刪除 東東.
最直接的解決方案是:使用collection的lock,讓這特別的客戶端獲得這個(gè)lock,進(jìn)行連續(xù)的數(shù)據(jù)獲取,然后再釋放lock.
public void foFetches(Hashtable ht){
synchronized(ht){
//具體的連續(xù)數(shù)據(jù)獲取動(dòng)作..
}
}
但是這一辦法可能鎖住Collection會(huì)很長(zhǎng)時(shí)間,這段時(shí)間,其他客戶端就不能訪問(wèn)該Collection了.
第二個(gè)解決方案是clone這個(gè)Collection,然后讓連續(xù)的數(shù)據(jù)獲取針對(duì)clone出來(lái)的那個(gè)Collection操作.這個(gè)方案前提是,這個(gè)Collection是可clone的,而且必須有提供深度clone的方法.Hashtable就提供了對(duì)自己的clone方法,但不是Key和value對(duì)象的clone,關(guān)于Clone含義可以參考專門(mén)文章.
public void foFetches(Hashtable ht){
Hashttable newht=(Hashtable)ht.clone();
}
問(wèn)題又來(lái)了,由于是針對(duì)clone出來(lái)的對(duì)象操作,如果原來(lái)的母體被其他客戶端操作修改了, 那么對(duì)clone出來(lái)的對(duì)象操作就沒(méi)有意義了.
最后解決方案:我們可以等其他客戶端修改完成后再進(jìn)行clone,也就是說(shuō),這個(gè)特別的客戶端先通過(guò)調(diào)用一個(gè)叫clone的方法來(lái)進(jìn)行一系列數(shù)據(jù)獲取操作.但實(shí)際上沒(méi)有真正的進(jìn)行對(duì)象拷貝,直至有其他客戶端修改了這個(gè)對(duì)象Collection.
使用Proxy實(shí)現(xiàn)這個(gè)方案.這就是copy-on-write操作.
Proxy應(yīng)用范圍很廣,現(xiàn)在流行的分布計(jì)算方式RMI和Corba等都是Proxy模式的應(yīng)用.
更多Proxy應(yīng)用,見(jiàn)http://www.research.umbc.edu/~tarr/cs491/lectures/Proxy.pdf
Sun公司的 Explore the Dynamic Proxy APIDynamic Proxy Classes