理解並使用設計模式,能夠培養我們良好的物件導向程式設計習慣,同時在實際應用中,可以如魚得水,享受游刃有餘的樂趣。
Proxy是比較有用途的一種模式,而且變種較多,應用場合覆蓋從小結構到整個系統的大結構,Proxy是代理的意思,我們也許有代理伺服器等概念,代理概念可以解釋為:在出發點到目的地之間有一道中間層,意為代理。
設計模式中定義:為其他物件提供一種代理以控制對這個物件的存取。
為什麼要使用代理模式
1.授權機制不同級別的用戶對同一對象擁有不同的訪問權利,如Jive論壇系統中,就使用Proxy進行授權機制控制,訪問論壇有兩種人:註冊用戶和遊客(未註冊用戶),Jive中就透過類似ForumProxy這樣的代理來控制這兩種用戶對論壇的存取權限。
2.某個客戶端不能直接操作到某個對象,但又必須和那個對像有所互動。
舉例兩個具體情況:
1.如果那個物件是一個是很大的圖片,需要花費很長時間才能顯示出來,那麼當這個圖片包含在文檔中時,使用編輯器或瀏覽器打開這個文檔,開啟文檔必須迅速,不能等待大圖片處理完成,這時需要做個圖片Proxy來取代真正的圖片。
2.如果那個物件在Internet的某個遠端伺服器上,直接操作這個物件因為網路速度原因可能比較慢,那我們可以先用Proxy來取代那個物件。
總之原則是,對於開銷很大的對象,只有在使用它時才創建,這個原則可以為我們節省很多寶貴的Java內存。所以,有些人認為Java耗費資源內存,我以為這和程式編制思路也有一定的關係。
如何使用代理模式
以Jive論壇系統為例,存取論壇系統的使用者有多種類型:註冊普通使用者、論壇管理者、系統管理者、遊客。註冊普通使用者才能發言,論壇管理者可以管理他被授權的論壇,系統管理者可以管理所有事務等,這些權限劃分和管理是使用Proxy完成的。
Forum是Jive的核心接口,在Forum中陳列了有關論壇操作的主要行為,如論壇名稱,論壇描述的獲取和修改,帖子發表刪除編輯等。
在ForumPermissions中定義了各種等級權限的使用者:
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中各種操作權限是和ForumPermissions定義的使用者層級有關係的,作為介面Forum的實作:ForumProxy正是將這種對應關係聯繫起來。例如,修改Forum的名稱,只有論壇管理者或系統管理者可以修改,程式碼如下:
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{ //只有是系统或论坛管理者才可以修改名称 if (permissions.isSystemOrForumAdmin()) { forum.setName(name); } else { throw new UnauthorizedException(); } } ... }
而DbForum才是介面Forum的真正實現,以修改論壇名稱為例:
public class DbForum implements Forum, Cacheable { ... public void setName(String name) throws ForumAlreadyExistsException { .... this.name = name; //这里真正将新名称保存到数据库中 saveToDb(); .... } ... }
凡是涉及到對論壇名稱修改這一事件,其他程式都必須先和ForumProxy打交道,由ForumProxy決定是否有權限做某一樣事情,ForumProxy是個名副其實的"網關","安全代理系統"。
在平時應用中,無可避免總要涉及到系統的授權或安全體系,不管你有無意識的使用Proxy,實際你已經在使用Proxy了。
我們繼續結合Jive談入深一點,下面要涉及到工廠模式了。
我們已經知道,使用Forum需要透過ForumProxy,Jive中建立一個Forum是使用Factory模式,有一個總的抽象類別ForumFactory,在這個抽象類別中,呼叫ForumFactory是透過getInstance()方法來實現,這裡使用了Singleton (也是設計模式之一),getInstance()回傳的是ForumFactoryProxy。
為什麼不返回ForumFactory,而返回ForumFactory的實作ForumFactoryProxy?原因是明顯的,需要透過代理程式確定是否有權限建立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{ //只有系统管理者才可以创建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互動操作。
注意到這裡有兩個Proxy:ForumProxy和ForumFactoryProxy。代表兩個不同的職責:使用Forum和創造Forum。至於為什麼將使用物件和建立物件分開,這也是為什麼使用Factory模式的原因所在:是為了"封裝" "分派"。換句話說,盡可能功能單一化,方便維護修改。
Jive論壇系統中其他如帖子的創建和使用,都是按照Forum這個思路而來的。
以上我們討論瞭如何使用Proxy進行授權機制的訪問,Proxy還可以對用戶隱藏另外一種稱為copy-on-write的優化方式。拷貝一個龐大而複雜的物件是一個開銷很大的操作,如果拷貝過程中,沒有對原來的物件有所修改,那麼這樣的拷貝開銷就沒有必要。用代理延遲這一拷貝過程。
比如:我们有一个很大的Collection,具体如hashtable,有很多客户端会并发同时访问它。其中一个特别的客户端要进行连续的数据获取,此时要求其他客户端不能再向hashtable中增加或删除 东东。
最直接的解决方案是:使用collection的lock,让这特别的客户端获得这个lock,进行连续的数据获取,然后再释放lock。
public void foFetches(Hashtable ht){ synchronized(ht){ //具体的连续数据获取动作.. } }
但是这一办法可能锁住Collection会很长时间,这段时间,其他客户端就不能访问该Collection了。
第二个解决方案是clone这个Collection,然后让连续的数据获取针对clone出来的那个Collection操作。这个方案前提是,这个Collection是可clone的,而且必须有提供深度clone的方法。Hashtable就提供了对自己的clone方法,但不是Key和value对象的clone。
public void foFetches(Hashtable ht){ Hashttable newht=(Hashtable)ht.clone(); }
问题又来了,由于是针对clone出来的对象操作,如果原来的母体被其他客户端操作修改了,那么对clone出来的对象操作就没有意义了。
最后解决方案:我们可以等其他客户端修改完成后再进行clone,也就是说,这个特别的客户端先通过调用一个叫clone的方法来进行一系列数据获取操作。但实际上没有真正的进行对象拷贝,直至有其他客户端修改了这个对象Collection。
使用Proxy实现这个方案,这就是copy-on-write操作。
Proxy应用范围很广,现在流行的分布计算方式RMI和Corba等都是Proxy模式的应用。
更多Java设计模式之代理模式(Proxy模式)介绍相关文章请关注PHP中文网!
相关文章: