Android設(shè)計模式06-代理模式

前言

今天分享的文章是關(guān)于設(shè)計模式中的代理模式,代理模式在Android中的應(yīng)用還是很廣的放祟,而且也是比較常用的一種設(shè)計模式恶迈。
本文主要是寫個簡單的demo來帶著大家熟悉認識代理模式光戈,最后舉例分析一個我們Android常用的網(wǎng)絡(luò)框架Retrofit中代理模式的使用杂抽,加深對代理模式的設(shè)計。

設(shè)計模式之代理模式

代理模式的定義

什么是代理模式生音?

  • 簡單一句話解釋:為其他對象提供代理對象宁否,以控制對這個對象的訪問
  • 詳細的解釋:為其他對象提供代理以控制對這個對象的訪問,代理對象起到了中介作用缀遍,不涉及功能服務(wù)慕匠,亦可增加額外的服務(wù);

代理模式的分類:

  • 遠程代理:為不同的地理對象提供局域網(wǎng)代表對象域醇。典型的設(shè)計有:C/S架構(gòu)屬于遠程代理的縮影
  • 虛擬代理:根據(jù)需要將資源消耗很大的對象進行延遲台谊,真正需要的時候再創(chuàng)建。典型設(shè)計:經(jīng)常我們看到很多APP在加載圖片的時候譬挚,會先加載一個默認的圖片锅铅,等真正的圖片加載完了之后再顯示出來,這樣非常的友好减宣。
  • 智能引用代理:提供對目標對象的額外的服務(wù)盐须。典型設(shè)計:現(xiàn)實場景中隨處可見,我們的火車蚪腋、汽車票代售處、代購等等都是屬于代理模式的范疇姨蟋。
  • 保護代理:控制用戶的訪問權(quán)限屉凯。典型設(shè)計:就像我們的公眾號的文章留言功能,只有你這個用戶關(guān)注了該公眾號之后才能留言眼溶,否則你就只能瀏覽不能留言悠砚。

代理模式的實現(xiàn)

Android 中比較常用的代理模式——智能引用代理,我們就以此為例堂飞,一步一步的來實現(xiàn)代理模式灌旧。
代理的實現(xiàn)方式主要有兩種:

靜態(tài)代理:

代理和被代理對象在代理之前都是被確定的,他們都實現(xiàn)相同的接口或者是繼承相同的抽象類绰筛,這里一張經(jīng)典的圖


上面這張圖我們可以看出:ProxySubject和RealSbuject都是繼承了Subject枢泰,Client請求Subjct的doSomething()方法,本應(yīng)該是調(diào)用RealSubject中的doSomething()方法铝噩,但是我們實際看到的卻是調(diào)用的ProxySubject方法中的doSomething()方法衡蚂,在ProxySubject方法中的doSomething()方法中再去調(diào)用RealSubject中的doSomething()方法。我們實際看到的是ProxySubject方法中的doSomething()方法,這樣就完成了代理毛甲。

靜態(tài)代理:

靜態(tài)代理之聚合的方式
1.按照之前的定義我們先定義一個接口:

public interface  Subject{
   public void doSomeThing();
}

2.創(chuàng)建被代理的角色年叮,邏輯執(zhí)行者:

public class RealSubject implements Subject{
   @Override
   public void doSomeThing() {
       //do some things
   }
}

3.創(chuàng)建我們的代理類:

public class Proxy implements Subject{
   private Subject subject = null;
   public Proxy(){
       subject = new RealSubject();
   }
   @Override
   public void doSomeThing() {
       subject.doSomeThing();
   }
}

可以看到我們在構(gòu)造方法里面創(chuàng)建了一個RealSubjecj的實現(xiàn)類對象,在調(diào)用Proxy的doSomething()方法的時候玻募,實際是調(diào)用的RealSubject對象的的doSomething()方法只损,但是經(jīng)常我們還會附加一些邏輯在里面比如我們需要打印一些Log日志,直接這樣加就可以了:

public class Proxy implements Subject{
   private Subject subject = null;
   public Proxy(){
       subject = new RealSubject();
   }
   @Override
   public void doSomeThing() {
       log.d("TAG","代理開始...")
       subject.doSomeThing();
       log.d("TAG","代理開結(jié)束...")
   }
}

這樣的好處就是正在實現(xiàn)類的邏輯不用做任何改變即可實現(xiàn)七咧。
寫到這里可能有很多的人有疑問跃惫,我直接創(chuàng)建RealSubject的實現(xiàn)類,調(diào)用doSomething()方法和你在代理中再創(chuàng)建調(diào)用有啥區(qū)別坑雅,反而增加了類和代碼辈挂。按照這個說法確實是如此,但是不要忘記了我們Android 的設(shè)計模式中是有六大原則:

  • 單一職責原則
  • 里氏替換原則
  • 依賴倒置原則
  • 接口隔離原則
  • 迪米特原則
  • 開閉原則
    這幾個原則不是我們的這篇文章重點要講述的裹粤,感興趣的朋友可自行查
    因此如果直接用RealSubject調(diào)用doSomething()终蒂,就不符合我們的設(shè)計模式的六大原則。
    靜態(tài)代理之繼承的方式
    和上面的聚合方式只有最后的代理類有點區(qū)別:
public class Proxy extends Subject{

  @Override
  public void doSomeThing() {
      log.d("TAG","代理開始...")
      super.doSomeThing();
      log.d("TAG","代理開結(jié)束...")
  }
}

這種方式和上訴的方式有著相同結(jié)果遥诉,也稱為靜態(tài)代理之繼承方式拇泣。
可能大家都可能會想到,既然靜態(tài)代理有兩種模式矮锈,那我們使用的時候到底應(yīng)該使用哪種呢霉翔?
其實這個問題我們先不說其他的,就以實現(xiàn)接口和繼承來說苞笨,必然是實現(xiàn)接口更加符合我們的設(shè)計模式债朵。接下來我舉個例子我想大家就應(yīng)該很清楚使用哪種方式更加的合適:

就以上面的例子來說,上面的繼承模式中我只添加了Log的功能瀑凝,假設(shè)我要為我們的代理增加兩個功能:1.計算時間
2.增加權(quán)限管理⌒蚵現(xiàn)在我們的代理類就又三個功能:1.Log能力 2.時間 3.權(quán)限。假設(shè)需求不斷的改變:1/2/3能力的順序不斷改變粤咪,我們是不是就要不斷的增加繼承的類谚中,所以這種方式并不是我們推薦的方式,也不符合我們的設(shè)計模式寥枝。因此宪塔,我們的聚合的方式是更加的適合靜態(tài)代理模式。

因此綜上所述囊拜,我們在使用靜態(tài)代理模式的時候某筐,我們更加的傾向于使用聚合的方式,也就是實現(xiàn)接口這種方式冠跷,而非繼承来吩。
可能有的小伙伴就會說了敢辩,聚合的方式到底該怎么實現(xiàn)呢?
創(chuàng)建接口

public interface ISubject {
   //出行
   public void doSomething();
}

實現(xiàn)接口真實執(zhí)行邏輯

public class RealSubject implements ISubject {
   private static final String TAG = "RealSubject";

   @Override
   public void doSomething() {
       Log.e(TAG, "doSomething: 執(zhí)行中");
   }
}

創(chuàng)建時間代理

public class TimeProxy implements ISubject {
   private static final String TAG = "TimeProxy";
   private ISubject iSubject;

   public TimeProxy(ISubject iSubject) {
       this.iSubject = iSubject;
   }

   @Override
   public void doSomething() {
       Log.e(TAG, "開始執(zhí)行時間...");
       iSubject.doSomething();
       Log.e(TAG, "結(jié)束執(zhí)行時間...");
   }

建log代理

public class LogProxy implements ISubject {
   private static final String TAG = "LogProxy";
   private ISubject iSubject;

   public LogProxy(ISubject iSubject) {
       this.iSubject = iSubject;
   }

   @Override
   public void doSomething() {
       Log.e(TAG, "日志開始弟疆。戚长。。");
       iSubject.doSomething();
       Log.e(TAG, "日志結(jié)束怠苔。同廉。。");
   }
}

我們在MainActivity中執(zhí)行兩種方式:

public class MainActivity extends AppCompatActivity {
   private static final String TAG = "MainActivity";
   @Override
   protected void onCreate(Bundle savedInstanceState) {
       super.onCreate(savedInstanceState);
       setContentView(R.layout.activity_main);
       RealSubject realSubject = new RealSubject();
       //先記錄時間  在打印日志
       ISubject logProxy = new LogProxy(realSubject);
       TimeProxy timeProxy = new TimeProxy(logProxy);
       timeProxy.doSomething();
       Log.e(TAG, "============================================ ");
       //先打印日志  再記錄時間
       TimeProxy timeProxy1 = new TimeProxy(realSubject);
       ISubject logProxy1 = new LogProxy(timeProxy1);
       logProxy1.doSomething();
   }

運行結(jié)果:

07-09 23:20:06.516 27723-27723/com.lt.proxy E/TimeProxy: 開始執(zhí)行時間...
07-09 23:20:06.516 27723-27723/com.lt.proxy E/LogProxy: 日志開始柑司。迫肖。。
07-09 23:20:06.516 27723-27723/com.lt.proxy E/RealSubject: doSomething: 執(zhí)行中
07-09 23:20:06.516 27723-27723/com.lt.proxy E/LogProxy: 日志結(jié)束攒驰。蟆湖。。
07-09 23:20:06.516 27723-27723/com.lt.proxy E/TimeProxy: 結(jié)束執(zhí)行時間...
07-09 23:20:06.516 27723-27723/com.lt.proxy E/MainActivity: ============================================ 
07-09 23:20:06.516 27723-27723/com.lt.proxy E/LogProxy: 日志開始玻粪。隅津。。
07-09 23:20:06.516 27723-27723/com.lt.proxy E/TimeProxy: 開始執(zhí)行時間...
07-09 23:20:06.516 27723-27723/com.lt.proxy E/RealSubject: doSomething: 執(zhí)行中
07-09 23:20:06.516 27723-27723/com.lt.proxy E/TimeProxy: 結(jié)束執(zhí)行時間...
07-09 23:20:06.516 27723-27723/com.lt.proxy E/LogProxy: 日志結(jié)束劲室。伦仍。。

復制代碼這樣我們就可以看出我們的聚合方式的靜態(tài)代理的優(yōu)點很洋。

到了這里我想很多的小伙伴都看到了一個缺點充蓝,要是我們的真實執(zhí)行邏輯的類RealSubject有很多種類型呢?例如我們今天要去上班喉磁,你會有多種方式:走路谓苟、騎自行車、開汽車协怒、坐地鐵涝焙、公交等等。斤讥。纱皆。很多的方式湾趾,難道我們每一個都要去實現(xiàn)一篇芭商,然后添加日志功能、時間功能搀缠?這樣是不是太不符合我們的代碼的設(shè)計模式了铛楣。因此,我們有沒有一種動態(tài)的方式艺普,傳入什么出行的工具簸州,那么就給我記錄什么工具呢鉴竭?那就是我們接下來要講解的動態(tài)代理模式,也是我們最重要的一種模式岸浑。

動態(tài)代理模式

學習動態(tài)代理之前搏存,首先我們要知道InvocationHandler類:

/**
* Created by Scorpio on 2018/7/9.
* 該類經(jīng)常稱之為事務(wù)處理類
*/

public class DynamicProxy implements InvocationHandler {
   /**
    *
    * @param proxy 代理類
    * @param method 被代理的方法
    * @param args 被代理方法的參數(shù)
    * @return 返回代理對象
    * @throws Throwable
    */
   @Override
   public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
       return null;
   }
}

InvocationHandler是個接口,只有一個抽象方法 invoke(Object proxy, Method method, Object[] args)矢洲,四個參數(shù)的意義:

  • param proxy 代理類
  • param method 被代理的方法
  • param args 被代理方法的參數(shù)
  • return 方法的返回值
    其次就是我們的Proxy即動態(tài)代理類璧眠,它當中有個方法:Proxy.newProxyInstance(ClassLoader loader,Class class,Proxy proxy)
public class RealSubject implements ISubject {
   private static final String TAG = "RealSubject";

   @Override
   public void doSomething() {
       Log.e(TAG, "doSomething: 執(zhí)行中");
   }
}

public class RealSubject2 implements ISubject {
   private static final String TAG = RealSubject2.class.getName();

   @Override
   public void doSomething() {
       Log.e(TAG, "doSomething: 執(zhí)行中");
   }
}

2.創(chuàng)建事務(wù)處理類

/**
* Created by Scorpio on 2018/7/9.
* 該類經(jīng)常稱之為事務(wù)處理類
*/

public class DynamicProxy implements InvocationHandler {
   private static final String TAG = "DynamicProxy";
   private Object target;

   public DynamicProxy(Object target) {
       this.target = target;
   }

   /**
    * @param proxy  代理類
    * @param method 被代理的方法
    * @param args   被代理方法的參數(shù)
    * @return 返回代理對象
    * @throws Throwable
    */
   @Override
   public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
       Log.e(target.getClass().getName(), "開始日志。读虏。责静。");
       method.invoke(target);
       Log.e(target.getClass().getName(), "結(jié)束日志。盖桥。灾螃。");
       return null;
   }
}

3.代碼中使用:

public class MainActivity extends AppCompatActivity {
   private static final String TAG = "MainActivity";

   @Override
   protected void onCreate(Bundle savedInstanceState) {
       super.onCreate(savedInstanceState);
       setContentView(R.layout.activity_main);

       Log.e(TAG, "===================RealSubject========================= ");
       RealSubject realSubject = new RealSubject();
       DynamicProxy dynamicProxy = new DynamicProxy(realSubject);
       ClassLoader classLoader = realSubject.getClass().getClassLoader();
       ISubject proxyInstance = (ISubject) Proxy.newProxyInstance(classLoader, realSubject.getClass().getInterfaces(), dynamicProxy);
       proxyInstance.doSomething();

       Log.e(TAG, "===================RealSubject2========================= ");
       RealSubject2 realSubject2 = new RealSubject2();
       DynamicProxy dynamicProxy2 = new DynamicProxy(realSubject2);
       ClassLoader classLoader2 = realSubject2.getClass().getClassLoader();
       ISubject proxyInstance2 = (ISubject) Proxy.newProxyInstance(classLoader2, realSubject2.getClass().getInterfaces(), dynamicProxy2);
       proxyInstance2.doSomething();
   }
}

運行結(jié)果:

07-10 00:07:30.284 30241-30241/com.lt.proxy E/MainActivity: ===================RealSubject========================= 
07-10 00:07:30.285 30241-30241/com.lt.proxy E/com.lt.proxy.RealSubject: 開始日志。揩徊。腰鬼。
07-10 00:07:30.285 30241-30241/com.lt.proxy E/RealSubject: doSomething: 執(zhí)行中
07-10 00:07:30.285 30241-30241/com.lt.proxy E/com.lt.proxy.RealSubject: 結(jié)束日志。靴拱。垃喊。
07-10 00:07:30.285 30241-30241/com.lt.proxy E/MainActivity: ===================RealSubject2========================= 
07-10 00:07:30.285 30241-30241/com.lt.proxy E/com.lt.proxy.RealSubject2: 開始日志。袜炕。本谜。
07-10 00:07:30.285 30241-30241/com.lt.proxy E/com.lt.proxy.RealSubject2: doSomething: 執(zhí)行中
07-10 00:07:30.285 30241-30241/com.lt.proxy E/com.lt.proxy.RealSubject2: 結(jié)束日志。偎窘。乌助。

復制代碼這樣我們的動態(tài)代理就實現(xiàn)成功了,我們不管傳入什么樣的實現(xiàn)類陌知,都可以通過動態(tài)代理獲取到代理對象他托,進而實現(xiàn)代理功能。

Android Retrofit 中的動態(tài)代理模式應(yīng)用

通過我上面對Retrofit源碼的截圖仆葡,以及上面的標注就可以看出赏参,這個Retrofit的creat()方法是個典型的動態(tài)代理的模式

總結(jié)

優(yōu)點:

1、代理模式能夠協(xié)調(diào)調(diào)用者和被調(diào)用者沿盅,在一定程度上降低了系統(tǒng)的耦合度把篓;
2、代理對象可以在客戶端和目標對象之間起到中介的作用腰涧,這樣起到了保護目標對象的作用韧掩。

缺點:

1、由于在客戶端和真實對象之間增加了代理對象窖铡,因此有些類型的代理模式可能會造成請求的處理速度變慢疗锐;
2坊谁、實現(xiàn)代理模式需要額外的工作,有些代理模式的實現(xiàn)非常復雜滑臊。

————————————————
版權(quán)聲明:本文為CSDN博主「lt_sc」的原創(chuàng)文章口芍,遵循CC 4.0 BY-SA版權(quán)協(xié)議,轉(zhuǎn)載請附上原文出處鏈接及本聲明雇卷。
原文鏈接:https://blog.csdn.net/qq_34560959/article/details/80981890

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末阶界,一起剝皮案震驚了整個濱河市,隨后出現(xiàn)的幾起案子聋庵,更是在濱河造成了極大的恐慌膘融,老刑警劉巖,帶你破解...
    沈念sama閱讀 222,681評論 6 517
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件祭玉,死亡現(xiàn)場離奇詭異氧映,居然都是意外死亡,警方通過查閱死者的電腦和手機脱货,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 95,205評論 3 399
  • 文/潘曉璐 我一進店門岛都,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人振峻,你說我怎么就攤上這事臼疫。” “怎么了扣孟?”我有些...
    開封第一講書人閱讀 169,421評論 0 362
  • 文/不壞的土叔 我叫張陵烫堤,是天一觀的道長。 經(jīng)常有香客問我凤价,道長鸽斟,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 60,114評論 1 300
  • 正文 為了忘掉前任利诺,我火速辦了婚禮富蓄,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘慢逾。我一直安慰自己立倍,他們只是感情好,可當我...
    茶點故事閱讀 69,116評論 6 398
  • 文/花漫 我一把揭開白布侣滩。 她就那樣靜靜地躺著口注,像睡著了一般。 火紅的嫁衣襯著肌膚如雪胜卤。 梳的紋絲不亂的頭發(fā)上疆导,一...
    開封第一講書人閱讀 52,713評論 1 312
  • 那天赁项,我揣著相機與錄音葛躏,去河邊找鬼澈段。 笑死,一個胖子當著我的面吹牛舰攒,可吹牛的內(nèi)容都是我干的败富。 我是一名探鬼主播,決...
    沈念sama閱讀 41,170評論 3 422
  • 文/蒼蘭香墨 我猛地睜開眼摩窃,長吁一口氣:“原來是場噩夢啊……” “哼兽叮!你這毒婦竟也來了?” 一聲冷哼從身側(cè)響起猾愿,我...
    開封第一講書人閱讀 40,116評論 0 277
  • 序言:老撾萬榮一對情侶失蹤鹦聪,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后蒂秘,有當?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體泽本,經(jīng)...
    沈念sama閱讀 46,651評論 1 320
  • 正文 獨居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 38,714評論 3 342
  • 正文 我和宋清朗相戀三年姻僧,在試婚紗的時候發(fā)現(xiàn)自己被綠了规丽。 大學時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 40,865評論 1 353
  • 序言:一個原本活蹦亂跳的男人離奇死亡撇贺,死狀恐怖赌莺,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情松嘶,我是刑警寧澤艘狭,帶...
    沈念sama閱讀 36,527評論 5 351
  • 正文 年R本政府宣布,位于F島的核電站翠订,受9級特大地震影響缓升,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜蕴轨,卻給世界環(huán)境...
    茶點故事閱讀 42,211評論 3 336
  • 文/蒙蒙 一港谊、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧橙弱,春花似錦歧寺、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 32,699評論 0 25
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至,卻和暖如春冯乘,著一層夾襖步出監(jiān)牢的瞬間蜜笤,已是汗流浹背点弯。 一陣腳步聲響...
    開封第一講書人閱讀 33,814評論 1 274
  • 我被黑心中介騙來泰國打工嗤练, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留榛了,地道東北人。 一個月前我還...
    沈念sama閱讀 49,299評論 3 379
  • 正文 我出身青樓煞抬,卻偏偏與公主長得像霜大,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子革答,可洞房花燭夜當晚...
    茶點故事閱讀 45,870評論 2 361

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