軟件設計模式學習(十六)代理模式


當直接訪問某些對象存在問題時,能夠經過一個代理對象來間接訪問,爲了保證客戶端使用的透明性,所訪問的真實對象與代理對象須要實現相同的接口。java


模式動機

某些狀況下,一個客戶不想或不能直接引用一個對象,此時能夠經過一個稱之爲代理的第三者實現間接引用。代理對象在客戶端和目標對象之間起到中介做用,而且能夠經過代理對象去掉客戶不能看到的內容和添加客戶須要的額外服務。程序員


模式定義

給某一個對象提供一個代理,並由代理對象控制對原對象的引用。代理模式的英文叫作 Proxy 或 Surrogate,它是一種對象結構模式。編程


模式結構

在這裏插入圖片描述

  1. Subject(抽象主題角色)數組

    聲明瞭真實主題和代理主題的公共接口,這樣一來在任何使用真實主題的地方均可以使用代理主題。客戶端針對抽象主題角色編程。ide

  2. Proxy(代理主題角色)工具

    代理主題角色內部包含對真實主題的引用,從而能夠在任什麼時候候操做真實主題角色。學習

    代理主題角色中提供一個與真實主題角色相同的接口,以便在任什麼時候候替代真實主體。測試

    代理主題角色還能夠控制對真實主題的使用,負責在須要時建立和刪除真實主題對象,並對真實主題對象的使用加以約束。優化

    代理角色一般在客戶端調用所引用的真實主題操做以前或以後執行其餘操做,而不只僅只是單純調用真實主題對象中的操做。this

  3. RealSubject(真實主題角色)

    真實主題角色定義了代理角色所表明的真實對象,真實主題角色中實現真實的業務,客戶端經過代理主題角色間接調用真實主題角色中定義的方法。


模式實例與解析

在一個論壇已註冊用戶和遊客權限不一樣,已註冊用戶擁有發帖、修改註冊信息、修改本身帖子等功能;而遊客只能看到別人發的貼子,沒有其餘權限。本實例中咱們使用代理模式中的保護代理,該代理用於控制對一個對象的訪問,能夠給不一樣用戶提供不一樣級別的使用權限。

在這裏插入圖片描述

  1. 抽象主題角色 AbstractPermission(抽象權限類)

    AbstractPermission 做爲抽象權限類,充當抽象主題角色,在其中聲明瞭真實主題角色所提供的業務方法,它是真實主題角色和代理主題角色的公共接口

    public interface AbstractPermission {
    
        public void modifyUserInfo();
    
        public void viewNote();
    
        public void publishNote();
    
        public void modifyNote();
    
        public void setLevel(int level);
    }
  2. 真實主題角色 RealPermission(真實權限類)

    RealPermission 是真實主題角色,它實現了在抽象主題角色中定義的方法,因爲種種緣由客戶端沒法訪問其中內容。

    public class RealPermission implements AbstractPermission {
    
        @Override
        public void modifyUserInfo() {
            System.out.println("修改用戶信息");
        }
    
        @Override
        public void viewNote() { }
    
        @Override
        public void publishNote() {
            System.out.println("發佈新帖");
        }
    
        @Override
        public void modifyNote() {
            System.out.println("修改發帖內容");
        }
    
        @Override
        public void setLevel(int level) { }
    }
  3. 代理主題角色 PermissionProxy(權限代理類)

    PermissionProxy 是代理主題角色,它也實現了抽象主題角色接口,同時在 PermissionProxy 中定義了一個 RealPermission 對象,用於調用 RealPermission 中定義的真實業務方法。經過引入 PermissionProxy 類來對系統的使用權限進行控制,這就是保護代理的用途。

    public class PermissionProxy implements AbstractPermission {
    
        private RealPermission permission = new RealPermission();
        private int level = 0;
    
        @Override
        public void modifyUserInfo() {
            if (0 == level) {
                System.out.println("對不起,你沒有該權限");
            } else if (1 == level) {
                permission.modifyUserInfo();
            }
        }
    
        @Override
        public void viewNote() {
            System.out.println("查看帖子");
        }
    
        @Override
        public void publishNote() {
            if (0 == level) {
                System.out.println("對不起,你沒有該權限");
            } else if (1 == level) {
                permission.publishNote();
            }
        }
    
        @Override
        public void modifyNote() {
            if (0 == level) {
                System.out.println("對不起,你沒有該權限");
            } else if (1 == level) {
                permission.modifyNote();
            }
        }
    
        @Override
        public void setLevel(int level) {
            this.level = level;
        }
    }
  4. 客戶端測試類 Client

    public class Client {
    
        public static void main(String[] args) {
    
            AbstractPermission permission = new PermissionProxy();
    
            permission.modifyUserInfo();
            permission.viewNote();
            permission.publishNote();
            permission.modifyNote();
    
            System.out.println("-------------------------");
    
            permission.setLevel(1);
            permission.modifyUserInfo();
            permission.viewNote();
            permission.publishNote();
            permission.modifyNote();
        }
    }
  5. 運行結果
    在這裏插入圖片描述

    代理類能夠對用戶訪問權限進行控制,所以有些用戶無權調用真實業務類的某些方法,當用戶權限改變時,則能夠訪問這些方法。若是須要增長並使用新的代理類,首先將新增代理類做爲抽象主題角色的子類,實如今抽象主題中聲明的方法。


模式優缺點

代理模式優勢以下:

  1. 代理模式能協調調用者和被調用者,在必定程度上下降了系統的耦合度
  2. 遠程代理使客戶端能訪問在遠程機器上的對象
  3. 虛擬代理經過使用一個小對象來表明一個大對象,能夠減小系統資源的消耗,對系統進行優化並提升速度
  4. 保護代理能夠控制對真實對象的使用權限

代理模式缺點以下:

  1. 因爲在客戶端和真實主題之間增長了代理對象,所以可能會形成請求的處理速度變慢。
  2. 有些代理模式的實現十分複雜。

模式使用環境

根據代理模式的使用目的,常見的代理模式有如下幾個類型。

  1. 遠程(Remote)代理:爲一個位於不一樣的地址空間的對象提供一個本地的代理對象,這個不一樣的地址空間能夠在同一臺主機,也能夠是另外一臺主機。
  2. 虛擬(Virtual)代理:若是須要建立一個資源消耗較大的對象,能夠先建立一個消耗較小的對象來表示,真實對象只在須要時纔會被真正建立。
  3. Copy-on-Write代理:它是虛擬代理的一種,把複雜操做延遲到只在客戶端真正須要時才執行。
  4. 保護(Protect or Access)代理:控制對一個對象的訪問,能夠給不一樣的用戶提供不一樣級別的使用權限。
  5. 緩衝(Cache)代理:爲某一個目標操做的結果提供臨時的存儲空間,以便多個客戶能夠共享這些結果。
  6. 防火牆(FireWall)代理:保護目標不讓惡意用戶接近
  7. 同步化(Synchronization)代理:使幾個用戶能同時使用一個對象而沒有衝突
  8. 智能(Smart Reference)引用:當一個對象被引用時,提供一些額外的操做,如將此對象被調用的次數記錄下來

靜態代理

所謂靜態代理,就是由程序員建立或特定工具自動生成源代碼,也就是在編譯時就已經將接口,被代理類,代理類等肯定下來。在程序運行以前,代理類的 .class 文件就已經生成。簡單來講,上述的實例就屬於靜態代理,PermissionProxy 代理類是咱們定義好的,在程序運行以前就已經編譯完成。


動態代理

傳統的代理模式中,客戶端經過 ProxySubject 調用 RealSubject 類的方法,同時還在代理類中封裝了其餘方法,能夠處理一些其餘問題。若是按照這種方法使用代理模式,那麼真實主題角色必須是是事先已經存在的,並將其做爲代理對象的內部成員屬性。若是一個真實主題角色必須對應一個代理主題角色,這將致使系統中的類的個數急劇增長,所以須要想辦法減小系統中類的個數。

Java 自帶的基於接口的動態代理(即只能實現接口的代理)能在運行時根據咱們在 Java 代碼中的指示動態生成的代理類,其實現相關類位於 java.lang.reflect 包,運行時動態地對某些東西做代理,主要涉及兩個類

  1. InvocationHandler 接口

    InvocationHandler is the interface implemented by the invocation handler of a proxy instance.
    Each proxy instance has an associated invocation handler.When a method is invoked on a proxy instance, the method invocation is encoded and dispatched to the invoke method of its invocation handler.

    InvocationHandler 是由代理實例的調用處理程序實現的接口。每一個代理實例都有一個關聯的調用處理程序。InvocationHandler 接口中定義了 invoke 方法,當代理實例調用某個方法時,該方法的調用將被編碼並調度到其調用處理程序的 invoke 方法處理。

    /**
     *	處理代理實例上的方法調用並返回結果
     *	proxy 表示動態代理類
     *	method 表示須要代理的方法
     *	args 表示代理方法的參數數組
     */
    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable;
  2. Proxy 類

    /**
     *	該類提供了用於爲接口建立代理實例的靜態方法
     */
    public class Proxy implements java.io.Serializable {
        ...
         /**
          *	根據傳入的接口類型返回一個動態建立的代理類實例
          *	loader 表示被代理類的類加載器
          * interfaces 表示被代理類實現的接口列表(與真實主題類的接口列表一致)
          *	h 表示所指派的調用處理程序類
          */
    	public static Object newProxyInstance(ClassLoader loader, Class<?>[] interfaces, InvocationHandler h) {
            ...
    }

    下面經過一個簡單實例來學習動態代理,如今有兩個真實主題類分別是 RealSubjectA 和 RealSubjectB,它們對於抽象主題類中定義的抽象方法 request() 提供了不一樣的實現,在不增長新的代理類的狀況下,使得客戶端經過一個代理類來動態選擇所代理的真實主題對象

    1. 抽象主題接口 AbstractSubject

      public interface AbstractSubject {
      
          public void request();
      
      }
    2. 真實主題類一 RealSubjectA

      public class RealSubjectA implements AbstractSubject {
      
          @Override
          public void request() {
              System.out.println("真實主題類A");
          }
      }
    3. 真實主題類二 RealSubjectB

      public class RealSubjectB implements AbstractSubject {
      
          @Override
          public void request() {
              System.out.println("真實主題類B");
          }
      }
    4. 動態代理類 DynamicProxy

      public class DynamicProxy implements InvocationHandler {
      
          private Object obj;
      
          public DynamicProxy() {}
      
          public DynamicProxy(Object obj) {
              this.obj = obj;
          }
      
          //  實現 invoke() 方法,調用在真實主題類中定義的方法
          @Override
          public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
      
              System.out.println("調用以前");
              //  利用反射調用方法,若是方法沒有返回值則爲 null
              Object result = method.invoke(obj, args);
              System.out.println("調用以後");
              return result;
          }
      }
    5. 客戶端測試類 Client

      public class Client {
      
          public static void main(String[] args) {
      
              AbstractSubject subject = new RealSubjectA();
              InvocationHandler handler = new DynamicProxy(subject);
              AbstractSubject subjectProxy = (AbstractSubject) Proxy.newProxyInstance(subject.getClass().getClassLoader(),
                      subject.getClass().getInterfaces(), handler);
              subjectProxy.request();
      
              System.out.println("-----------------------------");
      
              subject = new RealSubjectB();
              handler = new DynamicProxy(subject);
              subjectProxy = (AbstractSubject) Proxy.newProxyInstance(subject.getClass().getClassLoader(),
                      subject.getClass().getInterfaces(), handler);
              subjectProxy.request();
          }
      }
    6. 運行結果
      在這裏插入圖片描述

相關文章
相關標籤/搜索