大龍幫忙去談判---代理模式

anly_jun發表於2016-11-28

前情提要

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

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

所有示例原始碼已經上傳到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, 這就是我們今天要說的代理模式.
有了代理模式, 尤其是遠端代理, 小光發現有時候自己都可以不用親臨店了呢, 有了更多的時間出去考察新店地址了...哈哈.

相關文章