【阿里大神講設計模式】6.代理模式

本系列文章由阿里大神 **anly_jun **授權發布
140套Android優秀開源項目源碼,領取地址:http://mp.weixin.qq.com/s/afPGHqfdiApALZqHsXbw-A

[TOC]

前情提要

上集講到, 小光利用裝飾者模式調校好了飲品加料(糖, 冰, 蜂蜜...)的流程. 從此再也不怕客戶的各種要求了. 各式飲品也成了小光熱干面店的一大特色.

當然, 飲品的試喝也不是無期限了. 試喝期快結束了, 小光跟表妹商量了下, 結合顧客們的反饋, 他們選定了其中三家, 到底使用哪家還需要跟商家再談判下決定.

所有示例源碼已經上傳到Github, 戳這里

小光的煩惱

臨近和供應商的談判期了, 小光有點發怵了. 以往都是跟計算機打交道, 與人交道少, 何況還是商場...雖然自己是采購方, 不免還是有點虛.

先來看看要簽單的小光:

// 照例抽象了一個Person接口:
public interface Person {
    /**
     * 簽單
     * @param price
     */
    void signing(int price);
}

// 心底發虛的小光:
public class XiaoGuang implements Person {
    @Override
    public void signing(int price) {
        System.out.println("小光以" + price + "每箱的價格簽單.");
    }
}

大龍出場

于是, 小光找來了做建材生意的堂哥大龍, 讓大龍幫忙去談判. 大龍欣然接受, 帶著小光的底線就去了.

// 大龍
public class DaLong implements Person {

    private Person person;
    public DaLong(Person person) {
        this.person = person;
    }

    @Override
    public void signing(int price) {
        System.out.println("對方報價:" + price);
        if (price < 100) {
            this.person.signing(price);
        }
        else {
            negotiate(price);
        }
    }
    public void negotiate(int price) {
        System.out.println("不接受, 要求降價" + (price - 80));
    }
}

大龍也繼承自Person, 有簽單的職責. 但是除了signing, 大龍還肩負談判(negotiate)的職責. 在簽單上也有一些限制(小光給他的底線是100每箱). 當然, 談下來了, 簽字還是需要小光簽的.

談判開始

大龍雖然比小光大不了幾歲, 但可以說是商場老手了. 拿到小光的底線后, 他自己在這個基礎上再砍了20, 然后去跟飲品供應商談判了.

public class Demo {
    public static void main(String[] args) {
        DaLong daLong = new DaLong(new XiaoGuang());
        // 第一輪, 對方報價120.
        daLong.signing(120);
        // 第二輪, 對方報價100.
        daLong.signing(100);
        // 第三輪, 對方報價90.
        daLong.signing(90);
    }
}

酒桌上, 拉鋸三輪, 拿下:

// output
對方報價:120
不接受, 要求降價40

對方報價:100
不接受, 要求降價20

對方報價:90
小光以90每箱的價格簽單.

果然還是大龍技高一籌啊, 以比小光預期更少的價格成交.
小光也是從中學習到不少技巧...拜服大龍哥.

故事之后

照例, 故事之后, 我們用UML類圖來梳理下上述的關系:


相比于之前的關系, 這個相對簡單, 就兩個角色, 小光和大龍, 都實現了Person接口. 關鍵點在于:

  • 大龍是直接和供應商打交道的, 但是實際的決策和行為(簽單)是由小光來做的.
  • 也就是說大龍是小光的代理.

這就是我們所要說的代理模式:
為其他對象(小光)提供一個代理(大龍)以控制對這個對象的訪問.

在我們這個例子, 由于小光怯場, 不方便直接和供應商談判, 故而派出了代理大龍來直面供應商.

擴展閱讀一

細心的同學可能有發現, 這個例子的模式貌似和前文裝飾模式有點類似啊. 這里大龍也相當于給小光裝飾上了新的職責(談判negotiate):

public void negotiate(int price) {
    System.out.println("不接受, 要求降價" + (price - 80));
}

那么代理模式相比與裝飾模式有什么區別呢?

讓我們再帶上重點符來重溫下二者:

  • 代理模式旨在為一個對象提供一個代理, 來控制對這個對象的訪問.
  • 裝飾模式旨在為一個對象動態添加職責, 增加這個對象的行為/屬性.

二者雖然都會有代理類/裝飾類實際調用被代理對象/被裝飾對象的行為. 然而代理模式重在控制, 而裝飾模式重在添加.

例如本例中, 大龍代理了小光的簽單(signing)行為, 但不僅僅是像裝飾模式那樣, 加上某些行為/屬性后就交給小光處理的. 大龍還加了控制:

public void signing(int price) {
   System.out.println("對方報價:" + price);

   if (price < 100) {
       this.person.signing(price);
   }
   // 如果對方報價大于等于100的時候, 大龍并沒有讓小光處理.
   else {
       negotiate(price);
   }
}

如果對方報價大于等于100的時候, 大龍并沒有讓小光處理. 也就是說大龍是有控制權的.

擴展閱讀二

上面說到大龍是有控制權的, 也就是說, 這種代理實際上是一種控制代理, 也可以稱之為保護代理.

代理模式除了這種控制訪問/保護性的, 常常用到的場景還有:

遠程代理: 為一個在不同的地址空間的對象提供局部代表, 從而可以隱藏這個被代理對象存在于不同地址空間的事實. 這個代表有點類似于大使, 故而也可以稱之為"大使模式".
智能引用代理: 在代理中對被代理對象的每個操作做些額外操作, 例如記錄每次被代理對象被引用, 被調用的次數等. 有點像引用計數的感覺.

擴展閱讀三

說到遠程代理, 就有必要聊聊Android中著名的AIDL了. 熟悉AIDL的同學, 應該比較清晰了解了, AIDL就是一個典型的遠程代理模式的運用.

創建一個簡單的AIDL文件:

// IRemoteService.aidl
package com.anly.samples;

// Declare any non-default types here with import statements

interface IRemoteService {
    /**
     * Demonstrates some basic types that you can use as parameters
     * and return values in AIDL.
     */
    void signing(int price);
}

生成的文件IRemoteService.java文件如下:

/*
 * This file is auto-generated.  DO NOT MODIFY.
 * Original file: /Users/mingjun/Dev/my_github/AndroidLessonSamples/app/src/main/aidl/com/anly/samples/IRemoteService.aidl
 */
package com.anly.samples;
// Declare any non-default types here with import statements

public interface IRemoteService extends android.os.IInterface {
    /**
     * Local-side IPC implementation stub class.
     */
    public static abstract class Stub extends android.os.Binder implements com.anly.samples.IRemoteService {
        private static final java.lang.String DESCRIPTOR = "com.anly.samples.IRemoteService";

        /**
         * Construct the stub at attach it to the interface.
         */
        public Stub() {
            this.attachInterface(this, DESCRIPTOR);
        }

        /**
         * Cast an IBinder object into an com.anly.samples.IRemoteService interface,
         * generating a proxy if needed.
         */
        public static com.anly.samples.IRemoteService asInterface(android.os.IBinder obj) {
            if ((obj == null)) {
                return null;
            }
            android.os.IInterface iin = obj.queryLocalInterface(DESCRIPTOR);
            if (((iin != null) && (iin instanceof com.anly.samples.IRemoteService))) {
                return ((com.anly.samples.IRemoteService) iin);
            }
            return new com.anly.samples.IRemoteService.Stub.Proxy(obj);
        }

        @Override
        public android.os.IBinder asBinder() {
            return this;
        }

        @Override
        public boolean onTransact(int code, android.os.Parcel data, android.os.Parcel reply, int flags) throws android.os.RemoteException {
            switch (code) {
                case INTERFACE_TRANSACTION: {
                    reply.writeString(DESCRIPTOR);
                    return true;
                }
                case TRANSACTION_signing: {
                    data.enforceInterface(DESCRIPTOR);
                    int _arg0;
                    _arg0 = data.readInt();
                    int _result = this.signing(_arg0);
                    reply.writeNoException();
                    reply.writeInt(_result);
                    return true;
                }
            }
            return super.onTransact(code, data, reply, flags);
        }

        private static class Proxy implements com.anly.samples.IRemoteService {
            private android.os.IBinder mRemote;

            Proxy(android.os.IBinder remote) {
                mRemote = remote;
            }

            @Override
            public android.os.IBinder asBinder() {
                return mRemote;
            }

            public java.lang.String getInterfaceDescriptor() {
                return DESCRIPTOR;
            }

            @Override
            public int signing(int price) throws android.os.RemoteException {
                android.os.Parcel _data = android.os.Parcel.obtain();
                android.os.Parcel _reply = android.os.Parcel.obtain();
                int _result;
                try {
                    _data.writeInterfaceToken(DESCRIPTOR);
                    _data.writeInt(price);
                    mRemote.transact(Stub.TRANSACTION_signing, _data, _reply, 0);
                    _reply.readException();
                    _result = _reply.readInt();
                } finally {
                    _reply.recycle();
                    _data.recycle();
                }
                return _result;
            }
        }

        static final int TRANSACTION_signing = (android.os.IBinder.FIRST_CALL_TRANSACTION + 0);
    }

    public int signing(int price) throws android.os.RemoteException;
}

AIDL自動生成的java文件格式比較亂, 格式化了一下.
有幾個關鍵點:

  1. IRemoteService是一個接口, 有signing方法
  2. IRemoteService中有一個靜態抽象內部類Stub, 實現了IRemoteService接口.(集成了Binder再次就不討論了)
  3. Stub中有一個asInterface方法, 返回一個IRemoteService, 實際上是Proxy.
  4. Stub中有一個私有的內部類Proxy.
  5. 這個內部類的機制是為了增加內聚, 一個AIDL生成的文件理論上是一個業務服務體系, 故而放在一起.

我們實現下Proxy和Stub, 然后用UML圖來梳理下:


客戶端AidlSampleActivity:

// 綁定服務的ServiceConnection中獲取Proxy:
public void onServiceConnected(ComponentName name, IBinder service) {
  mRemoteService = IRemoteService.Stub.asInterface(service);
  isBound = true;

  if (mRemoteService != null) {
      try {
          mCurrentPrice = mRemoteService.signing(mCurrentPrice);
          mResult.setText("Result:" + mCurrentPrice);
      } catch (RemoteException e) {
          e.printStackTrace();
      }
  }
}

服務RemoteService繼承Stub生成的IBinder:

private IRemoteService.Stub mBinder = new IRemoteService.Stub() {
   @Override
   public int signing(int price) throws RemoteException {
       int signingPrice = price - 10;
       Log.d("mingjun", "signing: " + signingPrice);
       return signingPrice;
   }
};

為了更清晰表達這是一個遠程代理, 我們將RemoteService開辟在了其他進程中:

<service android:name=".aidl.RemoteService" android:process="com.anly.other"/>

本文不深入AIDL的應用和原理, 具體客戶端(AidlSampleActivity)和服務(RemoteService)的代碼就不貼了, 完整代碼點這里.
OK, 這就是我們今天要說的代理模式.
有了代理模式, 尤其是遠程代理, 小光發現有時候自己都可以不用親臨店了呢, 有了更多的時間出去考察新店地址了...哈哈.

最后編輯于
?著作權歸作者所有,轉載或內容合作請聯系作者
平臺聲明:文章內容(如有圖片或視頻亦包括在內)由作者上傳并發布,文章內容僅代表作者本人觀點,簡書系信息發布平臺,僅提供信息存儲服務。
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市,隨后出現的幾起案子,更是在濱河造成了極大的恐慌,老刑警劉巖,帶你破解...
    沈念sama閱讀 227,797評論 6 531
  • 序言:濱河連續發生了三起死亡事件,死亡現場離奇詭異,居然都是意外死亡,警方通過查閱死者的電腦和手機,發現死者居然都...
    沈念sama閱讀 98,179評論 3 414
  • 文/潘曉璐 我一進店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人,你說我怎么就攤上這事。” “怎么了?”我有些...
    開封第一講書人閱讀 175,628評論 0 373
  • 文/不壞的土叔 我叫張陵,是天一觀的道長。 經常有香客問我,道長,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 62,642評論 1 309
  • 正文 為了忘掉前任,我火速辦了婚禮,結果婚禮上,老公的妹妹穿的比我還像新娘。我一直安慰自己,他們只是感情好,可當我...
    茶點故事閱讀 71,444評論 6 405
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著,像睡著了一般。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發上,一...
    開封第一講書人閱讀 54,948評論 1 321
  • 那天,我揣著相機與錄音,去河邊找鬼。 笑死,一個胖子當著我的面吹牛,可吹牛的內容都是我干的。 我是一名探鬼主播,決...
    沈念sama閱讀 43,040評論 3 440
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了?” 一聲冷哼從身側響起,我...
    開封第一講書人閱讀 42,185評論 0 287
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后,有當地人在樹林里發現了一具尸體,經...
    沈念sama閱讀 48,717評論 1 333
  • 正文 獨居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內容為張勛視角 年9月15日...
    茶點故事閱讀 40,602評論 3 354
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發現自己被綠了。 大學時的朋友給我發了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 42,794評論 1 369
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖,靈堂內的尸體忽然破棺而出,到底是詐尸還是另有隱情,我是刑警寧澤,帶...
    沈念sama閱讀 38,316評論 5 358
  • 正文 年R本政府宣布,位于F島的核電站,受9級特大地震影響,放射性物質發生泄漏。R本人自食惡果不足惜,卻給世界環境...
    茶點故事閱讀 44,045評論 3 347
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧,春花似錦、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 34,418評論 0 26
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至,卻和暖如春,著一層夾襖步出監牢的瞬間,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 35,671評論 1 281
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人。 一個月前我還...
    沈念sama閱讀 51,414評論 3 390
  • 正文 我出身青樓,卻偏偏與公主長得像,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當晚...
    茶點故事閱讀 47,750評論 2 370

推薦閱讀更多精彩內容

  • 前情提要 上集講到, 小光利用裝飾者模式調校好了飲品加料(糖, 冰, 蜂蜜...)的流程. 從此再也不怕客戶的各種...
    anly_jun閱讀 955評論 5 11
  • Spring Cloud為開發人員提供了快速構建分布式系統中一些常見模式的工具(例如配置管理,服務發現,斷路器,智...
    卡卡羅2017閱讀 134,778評論 18 139
  • Android跨進程通信IPC整體內容如下 1、Android跨進程通信IPC之1——Linux基礎2、Andro...
    隔壁老李頭閱讀 10,798評論 13 43
  • 版權聲明:本文為博主原創文章,未經博主允許不得轉載 PS:轉載請注明出處作者: TigerChain地址: htt...
    TigerChain閱讀 1,721評論 0 2
  • 朝陽初上,夏日里難得的和風天氣,一早家人圍坐在一起吃了早餐,我便收拾好行囊準備出發,可愛的兒子一直舍不得我離開...
    瑜伽行者一瀾閱讀 212評論 0 0