跳到主要內容
黯羽輕揚每天積累一點點

設計模式之代理模式(Proxy Pattern)_補充篇

免費2015-03-07#Design_Pattern#代理模式#Proxy Pattern

前一篇關於代理模式的博文中詳細介紹了遠端代理,本篇將補充一些其他代理(虛擬代理、保護代理、防火牆代理……)

no_mkd

寫在前面:

代理模式的內部原理,作用及遠端代理的實作在上一篇博文中都做了詳細解釋,本文只是對其內容的補充,介紹其他代理

一.虛擬代理

首先,明確虛擬代理的作用:在巨大物件被真正建立出來之前,用虛擬代理來代替巨大物件(保證了巨大物件在需要的時候才被建立,避免資源浪費)。虛擬代理我們可能經常在用,只是不認識它而已,比如:

我們的程式中可能存在密集的計算或者從伺服器取得大量數據這樣耗時耗資源的操作,一般都會先顯示預設資訊,或者顯示動畫表示我們的程式還在努力工作,在操作完成之後顯示獲得的資訊。其中就用到了虛擬代理的思想,只是虛擬代理把等待操作完成期間的工作全部分離出來封裝到了一個虛擬代理物件裡面,與客戶直接互動的是虛擬代理而不是實際做事情的物件

虛擬代理接到客戶請求後做了這樣幾件事情:

  1. 如果實際物件不存在(尚未建立或者尚未建立完成)則記錄客戶請求,顯示預設資訊,等到實際物件建立完成之後把請求傳遞給實際物件
  2. 如果實際物件已經存在,則直接把客戶請求委託給實際物件
  3. 在實際物件沒有傳回操作結果之前,代理物件負責顯示預設資訊(或者做預設處理)
  4. 得到操作結果後直接傳遞給客戶(沒錯,代理的任務就是「交」與「接」)

既然是代理,那麼就要偽裝成實際物件,不能讓客戶發覺代理的存在,所以虛擬代理的類別圖是這樣的:

Client 持有一個虛擬代理物件 Proxy,Proxy 持有一個具體服務物件 MyService

而更重要的是:虛擬代理 Proxy 與具體服務 MyService 都擴充自 Service 介面,所以代理與實際物件具有相同的行為,客戶不會發覺 Proxy 的存在

二.保護代理

保護代理扮演著經紀人的角色(不是所有人都可以聯繫我背後的明星,你們只能聯繫我,在我確定你不是壞人之後,才給你轉接。。)

換句話說,保護代理為實際物件提供了存取控制,保證了只有合法的呼叫者才能得到服務

實作的話,類別圖和上面的虛擬代理完全一樣,只是 Proxy 多了一項判斷呼叫者權限的職責(發現什麼了嗎?我們好像可以把多個代理結合起來使用,當然這樣的代理不再滿足類別的單一責任原則,不過沒關係,我們完全可以建立一個代理的代理來管理一系列代理。。)

其實 Java 的 API 提供了對代理模式的支援(動態代理)。Java 提供的動態代理的動態體現在 Proxy 代理類別(沒錯,是類別,而不是物件)是在執行時才被建立的,我們不能直接修改 Proxy 的內部實作,但我們可以透過 InvocationHandler 來告訴 Proxy 應該怎麼做

採用動態代理來實作保護代理的基本思路是:建立多個代理,不同的代理表示不同權限的呼叫者對應的服務

那麼讓我們來設計一個情景,嘗試用動態代理實作保護代理:博文點讚機制:可以讚別人的博文,不能讚自己的博文,但博主和遊客都可以看到被讚數目

情景比較簡單,不過足夠說明問題了,下面開始模擬實作。首先要有 BlogUser:

package ProxyPattern.ProtectionProxy;

/**

  • 定義部落格使用者介面
  • @author ayqy */ public interface BlogUser { public void support(); public int getSupportCount(); }

還要有 ConcreteUser,其實作過程比較簡單,不在此展開。下面開始建立動態代理,這裡有兩種權限,博主與遊客,所以我們至少需要兩個不同的動態代理

AuthorHandler:

package ProxyPattern.ProtectionProxy;

import java.lang.reflect.InvocationHandler; import java.lang.reflect.InvocationTargetException; import java.lang.reflect.Method;

/**

  • 實作作作者服務的InvocationHandler

  • @author ayqy */ public class AuthorHandler implements InvocationHandler{ BlogUser user;

    public AuthorHandler(BlogUser user){ this.user = user; }

    @Override public Object invoke(Object proxy, Method method, Object[] args) throws IllegalAccessException {//宣告非法存取異常 try { /如果博主想要查看被讚數目,則委託給呼叫者user實作具體操作/ if(method.getName().equals("getSupportCount")){ return method.invoke(user, args); } /如果博主要給自己點讚,則丟出異常表示拒絕/ if(method.getName().equals("support")){ throw new IllegalAccessException(); } } catch (InvocationTargetException e) { // TODO Auto-generated catch block e.printStackTrace(); }

     	return null;//如果呼叫其他方法,直接無視
    

    }

}

與此類似,還需要實作 VisitorHandler。有了多個代理,我們還需要實作代理的代理,也就是保護代理(視呼叫者權限傳回一個對應的動態代理)

ProtectionProxy:

package ProxyPattern.ProtectionProxy;

import java.lang.reflect.Proxy;

/**

  • 實作保護代理

  • @author ayqy / public class ProtectionProxy { /*

    • @param user 呼叫者
    • @return 作者對應的代理 */ public BlogUser getAuthorProxy(BlogUser user){ return (BlogUser)Proxy.newProxyInstance(user.getClass().getClassLoader() , user.getClass().getInterfaces() , new AuthorHandler(user)); }

    /**

    • @param user 呼叫者
    • @return 遊客對應的代理 */ public BlogUser getVisitorProxy(BlogUser user){ return (BlogUser)Proxy.newProxyInstance(user.getClass().getClassLoader() , user.getClass().getInterfaces() , new VisitorHandler(user)); }

    /在此處插入生成其他代理的方法/ }

有了這些就足夠了,做一個簡單的測試:

package ProxyPattern.ProtectionProxy;

/**

  • 實作測試類別

  • @author ayqy */ public class Test { public static void main(String[] args){ //建立保護代理 ProtectionProxy proxy = new ProtectionProxy();

     System.out.println("博主部分:");
     //建立博主對應的代理
     BlogUser authorProxy = proxy.getAuthorProxy(new ConcreteUser());
     System.out.println("博主要查看被讚總數");
     System.out.println("被讚 " + authorProxy.getSupportCount() + " 次");
     //博主要給自己點讚
     System.out.println("博主要給自己點讚");
     try{
     authorProxy.support();
     }catch(Exception e){
     	System.out.println("點讚失敗,無法給自己點讚");
     }
     System.out.println("自讚之後,被讚 " + authorProxy.getSupportCount() + " 次");
    
     
     System.out.println("\n遊客部分:");
     //建立遊客對應的代理
     BlogUser vistorProxy = proxy.getVisitorProxy(new ConcreteUser());
     System.out.println("遊客要查看被讚總數");
     System.out.println("被讚 " + vistorProxy.getSupportCount() + " 次");
     //博主要給自己點讚
     System.out.println("遊客要給博主點讚");
     try{
     vistorProxy.support();
     }catch(Exception e){
     	System.out.println("點讚失敗,原因未知");
     }
     System.out.println("點讚之後,被讚 " + vistorProxy.getSupportCount() + " 次");
    

    } }

測試結果:

三.其他代理

代理模式應用很廣泛,無法一一列舉所有代理,在此簡單介紹幾種常見的代理:

代理名稱作用
防火牆代理控制網路資源的存取,保護主題免於「壞客戶」的侵害
智慧引用代理當主題被引用時,進行額外的動作,比如計算一個物件被引用的次數
快取代理為開銷大的運算結果提供暫時儲存,也允許多個客戶共享結果,以減少計算或網路延遲
同步代理在多執行緒的情況下為主題提供安全的存取
複雜隱藏代理(外觀代理)用來隱藏一個類別的複雜集合的複雜度,並進行存取控制(比外觀模式功能更強大)
寫入時複製代理用來控制物件的複製,方法是延遲物件的複製,直到真正需要複製為止(與虛擬代理類似)

當然,只要理解了最原始的遠端代理,遇到這些代理就都很容易理解。。

評論

暫無評論,快來發表你的看法吧

提交評論