設計模式 -- 代理模式 (Proxy Pattern)


代理模式也叫委托模式
定義:為其他對象提供一種代理以控制對這個對象的訪問杭攻。

代理模式通用的類圖如下:

代理模式 .png

我們來分析類圖中的幾個角色定義:
①Subject 抽象主題角色:可以是抽象類狮腿,也可以是接口聘芜,是一個最普通的業(yè)務類型定義拓提。
②RealSubject 真實主題角色:也叫做被委托角色棍厂,被代理角色豆拨,是Subject 抽象主題角色的具體實現(xiàn)者源葫,業(yè)務邏輯具體執(zhí)行者
③Proxy 代理主題角色:也叫做委托類雄坪,代理類产还。負責對真實角色的應用匹厘,把所有抽象主題委托的方法限制給真實主題角色實現(xiàn),并且在真實主題角色處理完畢前后做一些預處理和善后工作脐区。

通用代碼實現(xiàn)
Subject 抽象主題角色

public interface Subject {
   public void request();
}

RealSubject 真實主題角色

public class RealSubject implements Subject {
   @Override
   public void request() {
      //業(yè)務處理
   }
}

Proxy 代理主題角色:

public class Proxy implements Subject {
   private Subject subject = null;
   
   public Proxy(Subject subject) { //通過構造函數(shù)指定傳遞被代理者
      this.subject = subject;
   }
   
   @Override
   public void request() {
      this.Before();
      this.subject.request();
      this.After();
   }

  //預處理
   private void Before(){
      //代理前業(yè)務處理
   }

    //善后處理
   private void After(){
      //代理后業(yè)務處理
   }
}

一個代理類可以代理多個被委托者愈诚,因此我們需要在高層模塊中自己指定代理類具體代理哪個真實主題角色,也就是代碼中所寫的牛隅,通過構造函數(shù)傳遞被代理者炕柔。

代理模式的優(yōu)點
①職責清晰:真實的角色就是實現(xiàn)實際的業(yè)務邏輯,不用關系其他非本職責的事物媒佣。
②高擴展性:真實主題角色是隨時變化的匕累,不管它怎么變,只要是實現(xiàn)了接口默伍,都能被在不修改代理類下使用
③智能化:動態(tài)代理

場景模擬
使用代理模式實現(xiàn)代練公司給玩家代練游戲賬號
玩家接口(抽象主題角色):定義登錄欢嘿,殺怪衰琐,升級 三種行為

public interface IGamePlayer {
   //登錄賬戶名與密碼
   public void login(String username,String password);
   //殺怪
   public void killBoss();
   //升級
   public void upgrade();
}

玩家實現(xiàn)類(真實主題角色)

public class GamePlayer implements IGamePlayer{
   private String name = "";
   
   public GamePlayer(String name) {
      this.name = name;
   }
   
   @Override
   public void login(String username, String password) {
      System.out.println("登錄名為:"+username+" 密碼為:"+password+" 登錄成功");
   }
   
   @Override
   public void killBoss() {
      System.out.println(this.name+" 在打boss");
   }
   
   @Override
   public void upgrade() {
      System.out.println(this.name+ " 升級了!");
   }
}

代理類(代理主題角色類)

public class GamePlayerProxy implements IGamePlayer {
   private IGamePlayer gamePlayer = null;
   //通過構造函數(shù)傳遞對誰進行代練
   public GamePlayerProxy(IGamePlayer gamePlayer) {
      this.gamePlayer = gamePlayer;
   }
   
   @Override
   public void login(String username, String password) {
      this.gamePlayer.login(username,password);
   }
   
   @Override
   public void killBoss() {
      this.gamePlayer.killBoss();
   }
   
   @Override
   public void upgrade() {
      this.gamePlayer.upgrade();
   }
}

客戶端

public class Client {
   public static void main(String[] args) {
      System.out.println("---------未使用代理模式----------");
      IGamePlayer player = new GamePlayer("張三");
      player.login("zhangsan","123456");
      player.killBoss();
      player.upgrade();
      
      System.out.println("---------代理模式----------");
      GamePlayerProxy gamePlayerProxy = new GamePlayerProxy(player);
      gamePlayerProxy.login("張三","123456");
      gamePlayerProxy.killBoss();
      gamePlayerProxy.upgrade();
   }
}

代理模式的擴展:
類似于網(wǎng)絡代理服務器設置炼蹦,代理模式也分為:普通代理與強制代理
(1)普通代理:用戶必須知道代理的存在羡宙,通過代理尋找真實角色,也就是Proxy 代理主題角色這個類的存在是對用戶透明的掐隐。普通代理的要求是客戶端只能訪問代理角色狗热,而不能訪問RealSubject 真實主題角色。那么使用普通代理改造上述場景模擬虑省,代理類與玩家實現(xiàn)類修改如下
玩家實現(xiàn)類

public class GamePlayer implements IGamePlayer{
   private String name = "";
   //通過構造函數(shù)確認誰能創(chuàng)建真實對象
   public GamePlayer(IGamePlayer gamePlayer,String name) {
      if (gamePlayer == null){
         throw new RuntimeException("不能創(chuàng)建真實角色");
      }else {
         this.name = name;
      }
   }
   /.....以下省略...../

代理類

public class GamePlayerProxy implements IGamePlayer {
   private IGamePlayer gamePlayer = null;
   //通過構造函數(shù)傳遞對誰進行代練
   public GamePlayerProxy(String username) {
      try {
         //將代理類傳遞過去是為了檢查誰能創(chuàng)建真實角色
         this.gamePlayer = new GamePlayer(this,username);
      } catch (Exception e) {
         e.printStackTrace();
      }
   }
   /.....以下省略...../

客戶端

public class Client {
   public static void main(String[] args) {
      System.out.println("---------使用普通代理模式----------");
      IGamePlayer player = new GamePlayerProxy("張三");
      player.login("zhangsan","123456");
      player.killBoss();
      player.upgrade();
   }
}

經(jīng)過如上代碼修改斗搞,我們不需要知道真實角色是誰,屏蔽真實角色對高層模塊的影響慷妙,擴展性強。

(2)強制代理:與普通代理相反允悦,強制代理是通過真實角色尋找代理膝擂,否則無法訪問(無論是通過代理還是直接new一個主題類對象)。那么使用強制代理改造上述場景模擬隙弛,代碼修改如下

既然是通過真實角色獲取代理對象架馋,那么玩家類就需要修改
玩家接口修改

public interface IGamePlayer {
   //登錄賬戶名與密碼
   public void login(String username,String password);
   //殺怪
   public void killBoss();
   //升級
   public void upgrade();
   //獲取代理類
   public IGamePlayer getProxy();
}

玩家實現(xiàn)類修改

public class GamePlayer implements IGamePlayer{
   private String name = "";
   private IGamePlayer proxy = null;
   //通過構造函數(shù)確認誰能創(chuàng)建真實對象
   public GamePlayer(String name) {
      this.name = name;
   }
   
   @Override
   public void login(String username, String password) {
      if (this.isProxy()){
         System.out.println("登錄名為:"+username+" 密碼為:"+password+" 登錄成功");
      }else {
         System.out.println("請使用指定的代理訪問");
      }
   }
   
   @Override
   public void killBoss() {
      if(this.isProxy()){
         System.out.println(this.name+" 在打boss");
      }else {
         System.out.println("請使用指定的代理訪問");
      }
   }
   
   @Override
   public void upgrade() {
      if (this.isProxy()){
         System.out.println(this.name+ " 升級了!");
      }else {
         System.out.println("請使用指定的代理訪問");
      }
   }
   
   @Override
   public IGamePlayer getProxy() {
      //指定代理對像
      this.proxy = new GamePlayerProxy(this);
      return this.proxy;
   }
   
   //檢測是否是代理訪問
   private boolean isProxy(){
      if (this.proxy == null){
         return false;
      }else {
         return true;
      }
   }
}

代理類修改

public class GamePlayerProxy implements IGamePlayer {
   private IGamePlayer gamePlayer = null;
   //通過構造函數(shù)傳遞對誰進行代練
   public GamePlayerProxy(IGamePlayer gamePlayer) {
      this.gamePlayer = gamePlayer;
   }
   
   @Override
   public void login(String username, String password) {
      this.gamePlayer.login(username,password);
   }
   
   @Override
   public void killBoss() {
      this.gamePlayer.killBoss();
   }
   
   @Override
   public void upgrade() {
      this.gamePlayer.upgrade();
   }
   
   @Override
   public IGamePlayer getProxy() {
      //代理的代理沒有全闷,默認返回自己
      return this;
   }
}

客戶端:

public class Client {
   public static void main(String[] args) {
      System.out.println("---------直接訪問真實角色----------");
      IGamePlayer player1 = new GamePlayer("張三");
      player1.login("zhangsan","123456");
      player1.killBoss();
      player1.upgrade();
      
      System.out.println("---------直接訪問真實角色----------");
      IGamePlayer player2 = new GamePlayer("張三");
      IGamePlayer proxy = new GamePlayerProxy(player2);
      proxy.login("zhangsan","123456");
      proxy.killBoss();
      proxy.upgrade();
      
      System.out.println("---------使用強制代理模式----------");
      IGamePlayer player3 = new GamePlayer("張三");
      IGamePlayer proxy3 = player3.getProxy();
      proxy3.login("zhangsan","123456");
      proxy3.killBoss();
      proxy3.upgrade();
   }
}
-------------output--------------
---------直接訪問真實角色----------
請使用指定的代理訪問
請使用指定的代理訪問
請使用指定的代理訪問
---------直接訪問真實角色----------
請使用指定的代理訪問
請使用指定的代理訪問
請使用指定的代理訪問
---------使用強制代理模式----------
登錄名為:zhangsan 密碼為:123456 登錄成功
張三 在打boss
張三 升級了叉寂!

經(jīng)過上面代碼的演示,可以看出強制代理的概念就是要從真實角色查找到代理角色总珠,不允許直接訪問真實角色屏鳍。 在客戶端中使用getProxy方法就可以取得真實角色的代理,然后訪問真是角色的所有方法局服。

動態(tài)代理
定義:在實現(xiàn)階段不需要知道代理誰钓瞭,在運行階段才指定代理對象。
使用JDK提供的動態(tài)代理接口:java.lang.reflect.InvocationHandler淫奔,對被代理類的方法進行處理山涡。
具體代碼如下:

public class GamePlayIH implements InvocationHandler {
   //被代理者類
   Class cls = null;
   //被代理的實例對象
   Object obj = null;
   //通過構造函數(shù)傳遞被代理的實例對象
   public GamePlayIH(Object obj) {
      this.obj = obj;
   }
   //調(diào)用被代理的方法,invoke方法必須重寫唆迁,它完成對真實方法的調(diào)用
   @Override
   public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
      Object result = method.invoke(this.obj,args);
      return result;
   }
}

動態(tài)代理是根據(jù)被代理的接口獲取所有的方法鸭丛,也就是給定一個接口,動態(tài)代理就會自動實現(xiàn)該接口下的所有方法唐责,默認情況下所有方法返回值都為空值鳞溉,也就是雖然動態(tài)代理雖然實現(xiàn)接口下的所有方法,但是并沒有包含任何邏輯鼠哥,因此還需要通過InvocationHandler接口來處理所有方法的實際任務穿挨。

客戶端

public class Client {
   public static void main(String[] args) {
      //定義一個玩家
      IGamePlayer player = new GamePlayer("張三");
      //定義一個handler
      InvocationHandler handler = new GamePlayIH(player);
      //獲取類加載器
      ClassLoader classLoader = player.getClass().getClassLoader();
      //在這里給定IGamePlayer接口與InvocationHandler接口月弛,通過Proxy動態(tài)產(chǎn)生一個代理者
      IGamePlayer gamePlayerProxy = (IGamePlayer) Proxy.newProxyInstance(classLoader,new Class[]{IGamePlayer.class},handler);
      gamePlayerProxy.login("zhangsan","123456");
      gamePlayerProxy.killBoss();
      gamePlayerProxy.upgrade();
   }
}

讓我們來看看動態(tài)代理的模型:動態(tài)代理通用類圖

動態(tài)代理通用類圖.png

根據(jù)通用類圖,我們來寫出通用的模式代碼

public interface Subject {
   public void doSomething(String str);
}

public class RealSubject implements Subject {
   @Override
   public void doSomething(String str) {
      System.out.println("doSomething ---> "+str);
   }
}

public class MyInvocationHandler implements InvocationHandler {
   //被代理的實例對象
   Object obj = null;
   //通過構造函數(shù)傳遞被代理的實例對象
   public MyInvocationHandler(Object obj) {
      this.obj = obj;
   }
   //調(diào)用被代理的方法
   @Override
   public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
      Object result = method.invoke(this.obj,args);
      return result;
   }
}

public interface IAdvice {
   //通知只有一個執(zhí)行方法
   public void exec();
}

public class BeforeAdvice implements IAdvice {
   @Override
   public void exec() {
      System.out.println("前置通知執(zhí)行科盛!");
   }
}

public class DynamicProxy<T> {
   public static <T> T newProxyInstance(ClassLoader loader,Class<?>[]interfaces,InvocationHandler h){
      //尋找JoinPoint連接點帽衙,AOP框架使用元數(shù)據(jù)定義
      if (true){
         //執(zhí)行一個前置通知
         new BeforeAdvice().exec();
      }
      //執(zhí)行目標,并返回結果
      return (T)Proxy.newProxyInstance(loader,interfaces,h);
   }
}

客戶端

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);
      //執(zhí)行代理的行為
      proxy.doSomething("zhangsan");
   }
}

在這里注意看:

DynamicProxy.newProxyInstance(subject.getClass().getClassLoader(),subject.getClass().getInterfaces(),handler);

這段代碼贞绵。subject.getClass().getClassLoader():獲取subject對象的類加載器厉萝,subject.getClass().getInterfaces()是獲取Subject類的所有接口,最后指定代理handler后榨崩,會實現(xiàn)該類的所有方法谴垫,由MyInvocationHandler類中的invoke方法接管所有的方法實現(xiàn)。

因此proxy.doSomething("zhangsan")這段代碼的調(diào)用過程為:Client類 --> DynamicProxy代理類 --> InvocationHandler接口 --> MyInvocationHandler 實現(xiàn)類 --> 接管Subject接口的所有方法 --> RealSubject實現(xiàn)類

動態(tài)代理與靜態(tài)代理的區(qū)別:動態(tài)代理是面向切面編程母蛛,在不改變我們已有的代碼結構下增強或控制對象的行為翩剪。

注意:實現(xiàn)動態(tài)代理的首要條件是:被代理類必須實現(xiàn)一個接口,當然在CGLIB等技術上不需要接口也可以實現(xiàn)彩郊。

參考書籍:設計模式之禪 --- 秦小波 著

最后編輯于
?著作權歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末前弯,一起剝皮案震驚了整個濱河市,隨后出現(xiàn)的幾起案子秫逝,更是在濱河造成了極大的恐慌恕出,老刑警劉巖,帶你破解...
    沈念sama閱讀 206,602評論 6 481
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件违帆,死亡現(xiàn)場離奇詭異浙巫,居然都是意外死亡,警方通過查閱死者的電腦和手機刷后,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 88,442評論 2 382
  • 文/潘曉璐 我一進店門的畴,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人尝胆,你說我怎么就攤上這事苗傅。” “怎么了班巩?”我有些...
    開封第一講書人閱讀 152,878評論 0 344
  • 文/不壞的土叔 我叫張陵渣慕,是天一觀的道長。 經(jīng)常有香客問我抱慌,道長逊桦,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 55,306評論 1 279
  • 正文 為了忘掉前任抑进,我火速辦了婚禮强经,結果婚禮上,老公的妹妹穿的比我還像新娘寺渗。我一直安慰自己匿情,他們只是感情好兰迫,可當我...
    茶點故事閱讀 64,330評論 5 373
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著炬称,像睡著了一般汁果。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上玲躯,一...
    開封第一講書人閱讀 49,071評論 1 285
  • 那天据德,我揣著相機與錄音,去河邊找鬼跷车。 笑死棘利,一個胖子當著我的面吹牛,可吹牛的內(nèi)容都是我干的朽缴。 我是一名探鬼主播善玫,決...
    沈念sama閱讀 38,382評論 3 400
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼密强!你這毒婦竟也來了茅郎?” 一聲冷哼從身側響起,我...
    開封第一講書人閱讀 37,006評論 0 259
  • 序言:老撾萬榮一對情侶失蹤誓斥,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后许帐,有當?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體劳坑,經(jīng)...
    沈念sama閱讀 43,512評論 1 300
  • 正文 獨居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 35,965評論 2 325
  • 正文 我和宋清朗相戀三年成畦,在試婚紗的時候發(fā)現(xiàn)自己被綠了距芬。 大學時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 38,094評論 1 333
  • 序言:一個原本活蹦亂跳的男人離奇死亡循帐,死狀恐怖框仔,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情拄养,我是刑警寧澤离斩,帶...
    沈念sama閱讀 33,732評論 4 323
  • 正文 年R本政府宣布,位于F島的核電站瘪匿,受9級特大地震影響跛梗,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜棋弥,卻給世界環(huán)境...
    茶點故事閱讀 39,283評論 3 307
  • 文/蒙蒙 一核偿、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧顽染,春花似錦漾岳、人聲如沸轰绵。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,286評論 0 19
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽左腔。三九已至,卻和暖如春耀找,著一層夾襖步出監(jiān)牢的瞬間翔悠,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 31,512評論 1 262
  • 我被黑心中介騙來泰國打工野芒, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留蓄愁,地道東北人。 一個月前我還...
    沈念sama閱讀 45,536評論 2 354
  • 正文 我出身青樓狞悲,卻偏偏與公主長得像撮抓,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子摇锋,可洞房花燭夜當晚...
    茶點故事閱讀 42,828評論 2 345

推薦閱讀更多精彩內(nèi)容