天天看點

設計模式—— 十二 :代理模式什麼是代理模式?代理模式擴充代理模式優缺點

文章目錄

代理模式(Proxy Pattern)是一個使用率非常高的模式,其定義如下:

Provide a surrogate or placeholder for another object to control access to it.(為其他對象提供 一種代理以控制對這個對象的通路。)

代理模式是一種對象結構型模式。在代理模式中引入了一個新的代理對象,代理對象在客戶 端對象和目标對象之間起到中介的作用,它去掉客戶不能看到的内容和服務或者增添客戶需 要的額外的新服務。

代理模式的通用類圖如圖12-1所示:

圖10-1:代理模式通用類圖

根據類圖,代理模式包含三個角色:

● Subject:抽象主題角色,它聲明了真實主題和代理主題的共同接口,這樣一來在任何使用真實主題的地方都可以使用代理主題,用戶端通常需要針對抽象主題角色進行程式設計。

● RealSubject:具體主題角色也叫做被委托角色、被代理角色。它才是冤大頭,是業務邏輯的具體執行者。

● Proxy:代理主題角色,也叫做委托類、代理類。它負責對真實角色的應用,把所有抽象主題類定義的方法限制 委托給真實主題角色實作,并且在真實主題角色處理完畢前後做預處理和善後處理工作。

來看看具體的代碼實作。

  • Subject:抽象主題類

    抽象主題類聲明了真實主題類和代理類的公共方法,它可以是接口、抽象類或具體類。

public interface Subject {
    //定義了一個方法
   public  void request();
}      
  • RealSubject:真實主題類

    真實主題類繼承了抽象主題類,提供了業務方法的具體實作

public class RealSubject implements Subject{

    //實作方法
    @Override
    public void request() {
        //具體邏輯
    }

}      
  • Proxy:代理類

    代理類也是抽象主題類的子類,它對真實主題對象引用,調用在真實主題中實作的業務方法,在調用時可以在原有業務方法的基礎上附加一些新的方法來對功能進行擴充或限制

public class Proxy implements Subject{
    //要代理的類 
    private Subject subject=null;
    //通過構造方法傳遞被代理的類的執行個體
    public Proxy(Subject subject) {
        this.subject=subject;
    }
    
    //實作接口Subject中的方法
    @Override
    public void request() {
        this.before(); 
        //調用真實Subject中的方法
        this.subject.request(); 
        this.after();
    }

    //預處理
    private void before() {
        
    }
    
    //善後處理
    private void after() {
        
    }


}      

在實際開發過程中,代理類的實作比上述代碼要複雜很多,代理模式根據其目的和實作方式不同可分為很多種。

在網絡上代理伺服器設定分為透明代理和普通代理:

  • 透明代理就是使用者 不用設定代理伺服器位址,就可以直接通路,也就是說代理伺服器對使用者來說是透明的,不 用知道它存在的;
  • 普通代理則是需要使用者自己設定代理伺服器的IP位址,使用者必須知道代理 的存在。

設計模式中的普通代理和強制代理也是類似的一種結構,普通代理就是我們要知道代理的存在,然後才能通路;強制代理則是調用者直接調用真實角色,而不用關心代理是否存在,其代理的産生是由真實角色決定。

以代練代打遊戲舉例說明,普通代理,它的要求就是用戶端隻能通路代理角色,而不能通路真實角色,這是比較簡單的。以代練打遊戲更新的例子作為擴充,遊戲玩家,自己不練級 ,場景類不直接new一個GamePlayer對象了,由GamePlayerProxy來進行模拟場景。

圖12-2:普通代理類圖

設計模式—— 十二 :代理模式什麼是代理模式?代理模式擴充代理模式優缺點

具體代碼如下:

  • IGamePlayer:接口
public interface IGamePlayer {
    //登入遊戲 
    public void login(String user,String password); 
    //殺怪,網絡遊戲的主要特色
    public void killBoss();
    //更新 
    public void upgrade();
}      
  • GamePlayer:遊戲者

    在構造函數中,傳遞進來一個IGamePlayer對象,檢查誰能建立真實的角色

public class GamePlayer implements IGamePlayer{
    
    private String name = "";
    // 構造函數限制對象建立,并同時傳遞姓名
    public GamePlayer(IGamePlayer _gamePlayer, String _name) throws Exception {
        if (_gamePlayer == null) {
            throw new Exception("不能建立真實角色!");
        } else {
            this.name = _name;
        }
    }

    @Override
    public void login(String user, String password) {
        System.out.println("登入名為"+user + "的使用者" + this.name + "登入成功!");
        
    }

    @Override
    public void killBoss() {
        System.out.println(this.name + "在打怪!");
    }

    @Override
    public void upgrade() {
        System.out.println(this.name + " 又升了一級!");
    }

}      
  • GamePlayerProxy:代理者

    僅僅修改了構造函數,傳遞進來一個代理者名稱,即可進行代理,在這種改造下,系統 更加簡潔了,調用者隻知道代理存在就可以,不用知道代理了誰。

public class GamePlayerProxy implements IGamePlayer{
    private IGamePlayer gamePlayer = null;
    
    // 通過構造函數傳遞要對誰進行代練
    public GamePlayerProxy(String name){ 
        try { 
            gamePlayer = new GamePlayer(this,name); 
            } catch (Exception e) { 
                // TODO 異常處理 } }
        }
    }
    
    //代練登入
    @Override
    public void login(String user, String password) {
        this.gamePlayer.login(user, password);
    }

    //代練殺boss
    @Override
    public void killBoss() {
        this.gamePlayer.killBoss();
    }

    //代練更新
    @Override
    public void upgrade() {
        this.gamePlayer.upgrade();
    }

}      
  • Client:場景類
public class Client {

    /**
     * @param args
     */
    public static void main(String[] args) {
        //然後再定義一個代練者 
        IGamePlayer proxy = new GamePlayerProxy("鐵錘"); 
        //開始打遊戲,記下時間戳
        System.out.println("開始時間是:2020-04-10 22:10");
        proxy.login("zhangSan", "password"); 
        //開始殺怪 
        proxy.killBoss(); 
        //更新 
        proxy.upgrade(); 
        //記錄結束遊戲時間 
        System.out.println("結束時間是:2020-04-10 22:12");

    }

}      

在代理中,調用者隻知代理而不用知道真實的角色是誰,屏蔽了真實角色的變更對高層子產品的影響,真實的主題角色想怎麼修改就怎麼修改,對高層次的子產品沒有任何的影響,隻要實作了接口所對應的方法,該模式非常适合對擴充性要求較高的場合。當然,在實際的項目中,一般都是通過約定來禁止new一個真實的角色,這也是一個 非常好的方案。

強制代理在設計模式中比較另類,一般的思維都是通過代理找到真實 的角色,但是強制代理卻是要“強制”,必須通過真實角色查找到代理角色,否則你不能訪 問。不管是通過代理類還是通過直接new一個主題角色類,都不能通路,隻有通過真實角色指定的代理類才可以通路,也就是說由真實角色管理代理角色。

圖12-2:強制代理類圖

代碼如下:

  • 在接口上增加了一個getProxy方法,真實角色GamePlayer可以指定一個自己的代理,除 了代理外誰都不能通路。
public interface IGamePlayer {
    //登入遊戲 
    public void login(String user,String password); 
    //殺怪,網絡遊戲的主要特色
    public void killBoss();
    //更新 
    public void upgrade();
    //每個人都可以找一下自己的代理
    public IGamePlayer getProxy();
}
      
  • GamePlayer:強制代理的真實角色

    增加了一個私有方法,檢查是否是自己指定的代理,是指定的代理則允許通路,否則不允許通路。

public class GamePlayer implements IGamePlayer{
    
    private String name = "";
    
    //我的代理是誰 
    private IGamePlayer proxy = null;

    public GamePlayer(String _name) {
        this.name = _name;
    }

    //找到自己的代理
    @Override
    public IGamePlayer getProxy() {
        this.proxy = new GamePlayerProxy(this); 
        return this.proxy;
    }
    
    // 構造函數限制對象建立,并同時傳遞姓名
    public GamePlayer(IGamePlayer _gamePlayer, String _name) throws Exception {
        if (_gamePlayer == null) {
            throw new Exception("不能建立真實角色!");
        } else {
            this.name = _name;
        }
    }

    @Override
    public void login(String user, String password) {
        System.out.println("登入名為"+user + "的使用者" + this.name + "登入成功!");
        
    }

    @Override
    public void killBoss() {
        System.out.println(this.name + "在打怪!");
    }

    @Override
    public void upgrade() {
        System.out.println(this.name + " 又升了一級!");
    }


}      
  • GamePlayerProxy:強制代理的代理類
public class GamePlayerProxy implements IGamePlayer{
 
    private IGamePlayer gamePlayer = null;
    //構造函數傳遞使用者名
    public GamePlayerProxy(GamePlayer _gamePlayer) {
        this.gamePlayer = _gamePlayer;
    }

    @Override
    public void login(String user, String password) {
        gamePlayer.login(user, password);
    }

    @Override
    public void killBoss() {
        gamePlayer.killBoss();
    }

    @Override
    public void upgrade() {
        gamePlayer.upgrade();
    }

    //代理類沒有代理類,傳回自己
    @Override
    public IGamePlayer getProxy() {
        return this;
    }

}      
public class Client {
    public static void main(String[] args) {
        // 定義一個遊戲的玩家
        IGamePlayer gamePlayer = new GamePlayer("拳拳");
        // 擷取該玩家的代理
        IGamePlayer proxy = gamePlayer.getProxy();
        // 開始打遊戲,記下時間戳
        System.out.println("開始時間是:2020-04-10 22:10");
        proxy.login("quanquan", "password");
        // 開始殺怪
        proxy.killBoss();
        // 更新
        proxy.upgrade();
        // 記錄結束遊戲時間
        System.out.println("結束時間是:2020-04-10 22:12");
    }

}      

強制代理的概念就是要從真實角色查找到代理角色,不允 許直接通路真實角色。高層子產品隻要調用getProxy就可以通路真實角色的所有方法,它根本 就不需要産生一個代理出來,代理的管理已經由真實角色自己完成。

一個類可以實作多個接口,完成不同任務的整合。也就是說代理類不僅僅可以實作主題 接口,也可以實作其他接口完成不同的任務,而且代理的目的是在目标對象方法的基礎上作增強,這種增強的本質通常就是對目标對象的方法進行攔截和過濾。例如遊戲代理是需要收費的,升一級需要5元錢,這個計算功能就是代理類的個性,它應該在代理的接口中定義, 如圖12-3所示:

圖12-3:代理類的個性

設計模式—— 十二 :代理模式什麼是代理模式?代理模式擴充代理模式優缺點

增加了一個IProxy接口,其作用是計算代理的費用。

public interface IProxy { 
  //計算費用 
  public void count(); 
}      

GamePlayerProxy類實作該接口:

public class GamePlayerProxy implements IGamePlayer,IProxy { 
  private IGamePlayer gamePlayer = null; 
  //通過構造函數傳遞要對誰進行代練 
  public GamePlayerProxy(IGamePlayer _gamePlayer){ 
    this.gamePlayer = _gamePlayer; 
  }

 //代練殺怪 
  public void killBoss() { 
    this.gamePlayer.killBoss(); 
  }

  //代練登入 
  public void login(String user, String password) { 
   this.gamePlayer.login(user, password); 
  }

  //代練更新 
  public void upgrade() { 
    this.gamePlayer.upgrade(); 
    this.count(); 
  }

 //計算費用 
  public void count(){ 
   System.out.println("更新總費用是:150元"); 
  } 

}      

同時在upgrade方法中調用該方法,完成費用結算。

什麼是動态代理?動态代理是在實作階段不用關心代理誰,而在運作階段才指定代理哪一個對象。

相對來說,自己寫代理類的方式就是靜态代理。面向橫切面程式設計,也就是AOP(Aspect Oriented Programming),其核心就是采用了動态代理機制。

還是以打遊戲為例,類圖修改一下以實作動态代理,如圖12-4所示:

圖12-4:動态代理

設計模式—— 十二 :代理模式什麼是代理模式?代理模式擴充代理模式優缺點

在類圖中增加了一個InvocationHandler接口和GamePlayIH類,作用就是産生一個對象的代理對象,其中InvocationHandler是JDK提供的動态代理接口,對被代理類的方法進行代理。 我們來看程式,接口保持不變,實作類也沒有變化。

  • GamePlayIH:動态代理類

    其中invoke方法是接口InvocationHandler定義必須實作的,它完成對真實方法的調用。

     InvocationHandler接口——動态代理是根據被代理的接口生成所有的方法, 也就是說給定一個接口,動态代理會宣稱“我已經實作該接口下的所有方法了”,通過 InvocationHandler接口,所有方法都由該Handler來進行處理,即所有被代理的方法都由InvocationHandler接管實際的處理任務。

public class GamePlayIH implements InvocationHandler{
    //被代理者
    Class cls=null;
    //被代理的執行個體
    Object object=null;
    //我要代理誰
    public GamePlayIH(Object _obj) {
        this.object=_obj;
    }

    // 調用被代理的方法
    @Override
    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
        Object result = method.invoke(this.object, args);
        // 如果是登入方法,則發送資訊
        if (method.getName().equalsIgnoreCase("login")) {
            System.out.println("有人在用我的賬号登入!");
        }
        return result;
    }

}      

*Client: 場景類

public class Client {

    public static void main(String[] args) throws Throwable{
        //定義一個玩家
        IGamePlayer gamePlayer= new GamePlayer("辣個男人");
        //定義一個handler
        InvocationHandler handle=new GamePlayIH(gamePlayer);
        //開始打遊戲,記下時間戳 
        System.out.println("開始時間是:2020-04-10 10:45");
        //獲得類的class loader
        ClassLoader loader=gamePlayer.getClass().getClassLoader(); 
        //動态産生一個代理者
        IGamePlayer proxy=(IGamePlayer) Proxy.newProxyInstance(loader, new Class[] {IGamePlayer.class}, handle);
        //登入
        proxy.login("lagenanren", "password");
        //開始殺怪
        proxy.killBoss(); 
        //更新
        proxy.upgrade(); 
        //記錄結束遊戲時間
        System.out.println("結束時間是:2020-04-10 11:45");
    }

}      

運作結果:

在上面的動态代理類裡,遊戲登入時會通知。

這就是AOP程式設計。AOP程式設計沒有使用什麼新的技術,但是它對設計、編碼有非常大的影響,對于日志、事務、權限等都可以在系統設計階段不用考慮,而在設計後通 過AOP的方式切過去。

通用動态代理模型,類圖如圖 12-5所示:

圖12-5:動态代理通用類圖

設計模式—— 十二 :代理模式什麼是代理模式?代理模式擴充代理模式優缺點

動态代理實作代理的職責,業務邏輯Subject實作相關的 邏輯功能,兩者之間沒有必然的互相耦合的關系。通知Advice從另一個切面切入,最終在高層子產品也就是Client進行耦合,完成邏輯的封裝任務。

具體代碼實作如下:

  • Subject:抽象主題
public interface Subject {
    //業務操作
    public void doSomething(String str);
}      
  • RealSubject:真實主題
public class RealSubject implements Subject{

    @Override
    public void doSomething(String str) {
        System.out.println("do something!---->" + str);
    }

}      
  • MyInvocationHandler:動态代理的Handler類

    所有通過動态代理實作的方法全部通過invoke方法調

public class MyInvocationHandler implements InvocationHandler{
    
    //被代理的對象
    private Object target = null; 

    // 通過構造函數傳遞一個對象
    public MyInvocationHandler(Object _obj) {
        this.target = _obj;
    }

    @Override
    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
        //執行被代理的方法
        return method.invoke(this.target, args);
    }

}      
  • DynamicProxy:動态代理類
public class DynamicProxy<T> {
    public static <T> T newProxyInstance(ClassLoader loader, Class<?>[] interfaces, InvocationHandler h) {
        // 尋找JoinPoint連接配接點,AOP架構使用中繼資料定義
        if (true) {
            // 執行一個前置通知
            (new BeforeAdvice()).exec();
        }
        // 執行目标,并傳回結果
        return (T) Proxy.newProxyInstance(loader, interfaces, h);
    }

}      
  • 通知接口及實作
public interface IAdvice {
    //通知隻有一個方法,執行即可 
    public void exec();
}

public class BeforeAdvice implements IAdvice{

    @Override
    public void exec() {
        System.out.println("我是前置通知,我被執行了!");
    }

}      
  • 動态代理的場景類:
public class Client {

    public static void main(String[] args) {
        //定義一個主題 
        Subject subject = new RealSubject(); 
        //定義一個Handler
        InvocationHandler handler = new MyInvocationHandler(subject);
        //定義主題的代理 
        Subject proxy = DynamicProxy.newProxyInstance(subject.getClass(). getClassLoader(), subject.getClass().getInterfaces(),handler); 
        //代理的行為
        proxy.doSomething("完事了");
    }

}      
設計模式—— 十二 :代理模式什麼是代理模式?代理模式擴充代理模式優缺點

看看程式是怎麼實作的。在DynamicProxy類 中,有這樣的方法:

this.obj=Proxy.newProxyInstance(c.getClassLoader(),c.getInterfaces(),new MyInvocationHandler(_obj));      

該方法是重新生成了一個對象。注意 c.getInterfaces(),查找到該類的所有接口,然後實作接口的所有方法。當然了,方法都是空的,由誰具體負責接管呢?是new MyInvocationHandler(_Obj)這個對象。于是就知道一個類的動态代理類是這樣的一個類, 由InvocationHandler的實作類實作所有的方法,由其invoke方法接管所有方法的實作,其動态調用過程如圖12-6所示。

圖12-6: 動态代理調用過程示意圖

以上的代碼還有更進一步的擴充餘地,DynamicProxy類,它 是一個通用類,不具有業務意義,可以再産生一個實作類:

public class SubjectDynamicProxy extends DynamicProxy {
    public static <T> T newProxyInstance(Subject subject) {
        // 獲得ClassLoader
        ClassLoader loader = subject.getClass().getClassLoader();
        // 獲得接口數組
        Class<?>[] classes = subject.getClass().getInterfaces();
        // 獲得handler
        InvocationHandler handler = new MyInvocationHandler(subject);
        return newProxyInstance(loader, classes, handler);
    }
}
      

如此擴充以後,高層子產品對代理的通路會更加簡單:

public class Client1 {

    public static void main(String[] args) {
        //定義一個主題 
        Subject subject = new RealSubject(); 
        //定義主題的代理 
        Subject proxy = SubjectDynamicProxy.newProxyInstance(subject); 
        //代理的行為 
        proxy.doSomething("了了");
    }

}      

代理模式優點

  • 能夠協調調用者和被調用者,在一定程度上降低了系統的耦合度。
  • 用戶端可以針對抽象主題角色進行程式設計,增加和更換代理類無須修改源代碼,符合開閉原則,系統具有較好的靈活性和可擴充性。

代理模式缺點

  • 由于在用戶端和真實主題之間增加了代理對象,是以有些類型的代理模式可能會造成請求 的處理速度變慢,例如保護代理。
  • 實作代理模式需要額外的工作,而且有些代理模式的實作過程較為複雜,例如遠端代理。