前情提要
上集講到, 小光利用裝飾者模式調校好了飲品加料(糖, 冰, 蜂蜜...)的流程. 從此再也不怕客戶的各種要求了. 各式飲品也成了小光熱乾麵店的一大特色.
當然, 飲品的試喝也不是無期限了. 試喝期快結束了, 小光跟表妹商量了下, 結合顧客們的反饋, 他們選定了其中三家, 到底使用哪家還需要跟商家再談判下決定.
所有示例原始碼已經上傳到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檔案格式比較亂, 格式化了一下.
有幾個關鍵點:
- IRemoteService是一個介面, 有signing方法
- IRemoteService中有一個靜態抽象內部類Stub, 實現了IRemoteService介面.(整合了Binder再次就不討論了)
- Stub中有一個asInterface方法, 返回一個IRemoteService, 實際上是Proxy.
- Stub中有一個私有的內部類Proxy.
- 這個內部類的機制是為了增加內聚, 一個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, 這就是我們今天要說的代理模式.
有了代理模式, 尤其是遠端代理, 小光發現有時候自己都可以不用親臨店了呢, 有了更多的時間出去考察新店地址了...哈哈.