介紹
代理模式也稱為委託模式,是一種結構性設計模式。
說到代理,可能大部分人都會有一種陌生又熟悉的感覺,日常生活中好像都能遇到,比如代理上網,招商代理,商務代理等;但又說不出個具體的一二三來;代理這個事情如果我們換個角度,從委託者的角色出發,我們找代理上網,是因為我們在訪問某些網站時存在困難,需要有個角色來間接的幫我們實現這個功能;我們找商務代理,可能是因為許多事我們不在行或者其他原因,需要找專業的中間人來幫我們做事。因此,日常生活中我們更多扮演的是委託人的角色,代理以一種中間人的角色,幫我們是處理我們無能為力的事情。
如果從寫程式碼的角度出發,當我們遇到以下場景:
- 無法直接訪問某個物件
- 不想直接訪問某個物件
- 訪問某個物件存在困難
的時候,我們就可以通過一個代理,通過它來間接訪問真正的物件。
定義及UML圖
定義:
為目標物件提供一種代理,客戶端通過代理去訪問目標物件。
UML 圖
從代理模式的UML 類圖中,我們可以得到如下結論:
- 代理物件和委託物件需要實現相同的介面(抽象類)
- 代理物件持有委託物件的引用
可以看到,代理模式非常簡潔,總共就三個角色,包括抽象主題,委託者和代理者,下面用程式碼簡單實現一下基礎的代理模式。
public interface Subject {
void doSomething();
}
public class RealSubject implements Subject {
@Override
public void doSomething() {
System.out.println("This is real doSomeThing");
}
}
public class ProxySubject implements Subject {
private Subject mSubject;
// 代理類持有委託類的引用
public ProxySubject(Subject realSubject) {
mSubject = realSubject;
}
@Override
public void doSomething() {
mSubject.doSomething();
}
}
public class Client {
public static void main(String[] args) {
//建立委託類
Subject mRealSubject=new RealSubject();
//建立代理類
ProxySubject mProxy = new ProxySubject(mRealSubject);
//由代理類去做具體的操作
mProxy.doSomething();
}
}
複製程式碼
可以看到RealSubject和ProxySubject都實現了介面Subject。在客戶端使用ProxySubject的例項呼叫doSomething方法,而不是使用RealSubject的例項來實現。
你可能會好奇,這麼做的意義是什麼呢?直接用RealSubject的例項來呼叫doSomething方法不也可以嗎?何必多此一舉。試想,如果現在有很多個委託類,他們各自的實現都不同,客戶端只關心doSomething 的呼叫,而不關心具體的實現,這樣代理類就可以在其內部遮蔽委託類之間的差異了,這也是客戶端不想關注的事情。這麼說可能有點暈,下面就通過Android原始碼中的實現來感受一下。
Android 中的代理模式
平時寫程式碼的時候,可能感覺代理模式沒怎麼遇到過。其實不然,甚至可以說代理模式是我們最常用到的一種設計模式。這裡就來看看幾乎天天都在使用的AppCompatActivity。
最早的時候,我們建立自己的Activity都是直接繼承android.app.Activity。後來隨著Android版本的升級,我們建立的Activity會繼承AppCompatActivity。這裡的Compat其實就是Compatible(相容)的縮寫,那麼他是怎麼實現相容的呢。
onCreate
onCreate()方法是整個Activity生命週期的開始。AppCompatActivity又是怎麼實現他的呢。
AppCompatActivity-onCreate()
@Override
protected void onCreate(@Nullable Bundle savedInstanceState) {
final AppCompatDelegate delegate = getDelegate();
delegate.installViewFactory();
delegate.onCreate(savedInstanceState);
……
}
複製程式碼
可以看到這裡他並沒具體去實現onCreate,而是使用一個AppCompatDelegate例項的onCreate()方法去實現。繼續看getDelegate 的實現。
AppCompatActivity-getDelegate()
@NonNull
public AppCompatDelegate getDelegate() {
if (mDelegate == null) {
mDelegate = AppCompatDelegate.create(this, this);
}
return mDelegate;
}
複製程式碼
可以看到這個例項建立是在AppCompatDelegate類中。接著看create的實現
AppCompatDelegate-create()
public static AppCompatDelegate create(Activity activity, AppCompatCallback callback) {
return create(activity, activity.getWindow(), callback);
}
private static AppCompatDelegate create(Context context, Window window,
AppCompatCallback callback) {
if (Build.VERSION.SDK_INT >= 24) {
return new AppCompatDelegateImplN(context, window, callback);
} else if (Build.VERSION.SDK_INT >= 23) {
return new AppCompatDelegateImplV23(context, window, callback);
} else if (Build.VERSION.SDK_INT >= 14) {
return new AppCompatDelegateImplV14(context, window, callback);
} else if (Build.VERSION.SDK_INT >= 11) {
return new AppCompatDelegateImplV11(context, window, callback);
} else {
return new AppCompatDelegateImplV9(context, window, callback);
}
}
複製程式碼
可以看到,這裡就不同的Android版本,分別返回了不同的AppCompatDelegate。如果去看原始碼(這裡的原始碼分析不是重點,就不貼出了,直接給出結論),我們會發現,從 AppCompatDelegateImplN到AppCompatDelegateImplV9,是子類到父類的關係,之間是依次繼承。而AppCompatDelegateImplV9又繼承自AppCompatDelegateImplBase(抽象類),而這個AppCompatDelegateImplBase則是繼承自AppCompatDelegate。
到這裡,結合一開始我們所說的代理模式的內容,我們很容易總結出以下結論:
- AppCompatDelegate 同時兼顧了抽象主題和代理類的角色
- AppCompatDelegateImplN,AppCompatDelegateImplV23等這些都是委託類,他們都繼承自 AppCompatDelegate 類。
通過AppCompatDelegate.java(點選可直接檢視)的原始碼,我們可以發現,這個抽象類內部定義了一系列和 Activity 相關的抽象方法,包括Activity生命週期函式,setContentView,setSupportActionBar等。我們知道,子類通過繼承父類,可以擴充套件(spuer)或直接覆蓋父類的方法實現。 AppCompatDelegateImplV9 這個類是AppCompatDelegate的具體實現,之後的版本,就可以通過繼承AppCompatDelegateImplV9來擴充套件或修改一些方法實現,通過AppCompatDelegate 在create方法中建立不同的委託類來完成不同的實現,而我們原先寫好的程式碼也不會被破壞,可以看到Android原始碼對Activity相容這個事做的非常巧妙。AppCompatDelegate主要是對ActionBar的相容及夜間模式的處理做了一些方便開發者實現的處理;這裡就不再具體分析了。
當然,代理模式這個幾乎找不到缺點的設計模式,在Android原始碼中的應用也是比較廣泛,基本上關於相容性的實現,都會用到以上思路,比如NotificationCompatImpl幾乎使用了和AppCompatDelegate同樣的思路,實現了在手機通知欄中實現不同的通知樣式。除了相容性的處理,另外一個比較經典的實現就是Binder了,作為跨程式通訊的核心理念,Binder巧妙的使用了代理模式,處理了我們無法在不同應用之間共享和傳遞資料的問題。關於Binder的分析,網上真的太多了,這裡就不再贅述了,有興趣的同學可以看看這篇代理模式在Binder中的使用.
動態代理
以上的分析中,代理類是由我們直接建立好的;現實中可能還有這樣一種場景,代理類並不是在程式編譯的時候建立,而是在執行的過程中通過Java的反射機制動態的進行建立,這樣的代理模式成為動態代理,對應的之前我們所說的就是靜態代理了。
其實,動態代理的實現沒有什麼可說的,說白了都是模板程式碼,Java為開發者提供了InvocationHandler,實現該介面重寫其invoke 方法即可。
還是以之前的Subject為例
public interface Subject {
void doSomething();
}
public class RealSubject implements Subject {
@Override
public void doSomething() {
System.out.println("This is real doSomeThing");
}
}
public class DynamicProxyHandler implements InvocationHandler {
private Object mObject;
public DynamicProxyHandler(Object object) {
mObject = object;
}
@Override
public Object invoke(Object o, Method method, Object[] objects) throws Throwable {
return method.invoke(mObject, objects);
}
}
public class MainClass {
public static void main(String[] args) {
// 委託類
Subject mRealSubject = new RealSubject();
// 委託類classLoader
ClassLoader mClassLoader = mRealSubject.getClass().getClassLoader();
// 委託類對應的ProxyHandler
DynamicProxyHandler mProxyHandler = new DynamicProxyHandler(mRealSubject);
Class[] mClasses = new Class[]{Subject.class};
// 代理類
Subject proxySubject = (Subject) Proxy.newProxyInstance(mClassLoader, mClasses, mProxyHandler);
// 代理類呼叫方法
proxySubject.doSomething();
}
}
複製程式碼
這裡可以看到,DynamicProxyHandler 內部持有的並不是一個具體的物件,而是 Object 類,而在其 invoke 方法中,又會根據具體的 Object 物件及引數呼叫其對應的方法。這樣當我們在客戶端呼叫時,完全是根據委託類通過 Proxy.newProxyInstance 方法動態的建立代理類。在上面的程式碼中,我們是通過委託類 RealSubject 動態的建立了一個代理類,通過代理類呼叫抽象主題中定義好的方法,實際上就會呼叫委託類中的具體實現。而在Java中,我們可以通過反射機制,動態的建立類及其例項,因此,我們便可以在執行時通過不同的委託類,更靈活的建立代理類,從而實現不同的功能。
關於動態代理,這篇十分鐘理解Java之動態代理分析的非常好,有興趣的同學可以再看看。
在Android中,關於動態代理的使用,最經典的莫過於這幾年最火熱的Retrofit了。這裡可以簡單看一下。
public interface GitHubService {
@GET("users/{user}/repos")
Call<List<Repo>> listRepos(@Path("user") String user);
}
GitHubService service = retrofit.create(GitHubService.class);
Call<List<Repo>> repos = service.listRepos("octocat");
複製程式碼
上面的實現,現在大家應該很熟悉了,當我們用Retrofit例項,呼叫其create方法時,到底發生了什麼呢?
public <T> T create(final Class<T> service) {
Utils.validateServiceInterface(service);
if (validateEagerly) {
eagerlyValidateMethods(service);
}
return (T) Proxy.newProxyInstance(service.getClassLoader(), new Class<?>[] { service },
new InvocationHandler() {
private final Platform platform = Platform.get();
@Override public Object invoke(Object proxy, Method method, @Nullable Object[] args)
throws Throwable {
// If the method is a method from Object then defer to normal invocation.
if (method.getDeclaringClass() == Object.class) {
return method.invoke(this, args);
}
if (platform.isDefaultMethod(method)) {
return platform.invokeDefaultMethod(method, service, proxy, args);
}
ServiceMethod<Object, Object> serviceMethod =
(ServiceMethod<Object, Object>) loadServiceMethod(method);
OkHttpCall<Object> okHttpCall = new OkHttpCall<>(serviceMethod, args);
return serviceMethod.callAdapter.adapt(okHttpCall);
}
});
}
複製程式碼
可以看到,這裡就是一個典型的動態代理實現,通過serviceMethod.callAdapter.adapt返回了一個service物件的代理物件,在上面的例子裡,就是返回了一個GitHubService的代理物件,這樣我們就可以通過這樣一個物件去呼叫GitHubService中定義好的各種網路請求,而不用在使用的時候再去決定是POST請求還是GET請求,引數是放在Body裡還是params裡,因為Retrofit 通過把反射註解和動態代理的巧妙結合,遮蔽了複雜的引數拼接操作,把所有我們需要對OKHttp的進行傳遞的引數,動態的幫我們傳遞了,一旦在介面中定義好了使用方式,就可以非常方便的獲取到okhttp中最關鍵的Call了,有了Call我們就可以通過execute或者是enqueue發起網路請求了。
總結
以上就是對代理模式的分析,總的來說代理模式的結構非常簡單;包括抽象主題,委託類,代理類三個核心角色,從大的方向上可以分為靜態代理和動態代理兩大類;通過靜態代理的方式,在開發迭代的過程中,為實現相容性提供了一種非常友好的實現思路;在日常開發中,如果我們使用的物件之間有著強烈的耦合,可是思考一下是否可以通過代理模式解耦;同時,當我們需要擴充套件某個類的部分功能時,但又不想去破壞原有的功能或者是根本無法修改時,我們可以考慮代理模式,但也要明白,通過代理模式我們能做的也只能是功能擴充套件,想要更新委託類中已經實現的內容他是無能為力的。
動態代理,可以根據執行時的委託類動態的生成代理類,這樣就減輕了代理類的負擔,避免在編碼階段就具體的委託類再做各種判斷了。
代理模式很簡單,也很實用,但不要忘記代理類和委託類需要實現功能的介面或抽象類,不要忽略了這一點。
好了,關於代理模式的分析就到這裡了