面向接口編程
面向接口編程,模塊化編程的必備技能,其乃實現(xiàn)解耦,增強擴展性的重要手段。
面向接口編程具體指的是什么呢?
首先說一下什么是面向?qū)ο缶幊?,大家都知道,面向?qū)ο缶幊淌窍鄬τ诿嫦蜻^程編程來說的,基本上,具有對象概念的程序設(shè)計都可以稱為面向?qū)ο缶幊?。而面向接口編程僅僅是面向?qū)ο缶幊痰囊环N模塊化實現(xiàn)形式,其從組件的角度來進行代碼設(shè)計,將抽象與實現(xiàn)分離。
接口泛指實體把自己提供給外界的一種抽象物,利用由內(nèi)部操作分離出的外部溝通方法,使得實體能被修改內(nèi)部而不影響外界其他實體與自己交互的方式。面向接口編程中的“接口”,在Java語言中,不僅僅是“interface”關(guān)鍵字這么簡單,interface、abstract class以及普通的class都能成為所謂的接口。
interface約定的是務(wù)必實現(xiàn)的方法,強調(diào)的是規(guī)則的制定。abstract class則是在抽象的同時允許提供一些默認的行為,以達到代碼復(fù)用的效果。一個實現(xiàn)類(相對于抽象而言)可以實現(xiàn)多個interface,而只能繼承一個abstract class。
面向接口編程能夠帶來什么呢?
面向接口編程可以降低代碼間的耦合,增強代碼的擴展性,而正是這種特性,使得多人同時開發(fā)變成了可能。其實大部分設(shè)計模式就是面向接口編程很好的例子。
面向接口編程如何實現(xiàn)呢?
進行面向接口編程實操時,我們一般注意三點:先定義接口(接口或者抽象類),再定義實現(xiàn)類;在函數(shù)間傳入傳出的是接口而不是實現(xiàn)類;一方只認識另一方的接口,想進入另一方,就去調(diào)用另一方所披上的接口外套。
從下圖中我們可以看到Retrofit用到了哪些接口類:

設(shè)計模式
我們從Retrofit的調(diào)用流程來分析用到的設(shè)計模式
創(chuàng)建Retrofit對象
Retrofit retrofit = new Retrofit.Builder()
.baseUrl(API_URL)
.addConverterFactory(GsonConverterFactory.create())
.build();
建造者模式--摘自《Java與模式》
建造者模式可以將一個產(chǎn)品的內(nèi)部表象與產(chǎn)品的生成過程分割開來,從而可以使一個建造過程生成具有不同的內(nèi)部表象的產(chǎn)品對象。
在很多情況下,建造者模式實際上是將一個對象的性質(zhì)建造過程外部化到獨立的建造者對象中,并通過一個導(dǎo)演者角色對這些外部化的性質(zhì)賦值過程進行協(xié)調(diào)。
以下情況使用建造者模式
- 需要生成的產(chǎn)品對象有復(fù)雜的內(nèi)部結(jié)構(gòu)
- 需要生成的產(chǎn)品對象的屬性相互依賴
- 需要生成的產(chǎn)品對象有多個可選的構(gòu)造參數(shù)
- 防止生成的產(chǎn)品對象的參數(shù)被再次修改
創(chuàng)建網(wǎng)絡(luò)請求接口的實例
GitHub github = retrofit.create(GitHub.class);
門面模式--摘自《Java與模式》
門面模式要求一個子系統(tǒng)的外部與其內(nèi)部的通信必須通過一個統(tǒng)一的門面(Facade)對象進行,門面提供一個高層次的接口,使得子系統(tǒng)更易于使用
門面模式的門面類將客戶端與子系統(tǒng)的內(nèi)部復(fù)雜性分隔開,使得客戶端只需要與門面對象打交道,而不需要與子系統(tǒng)內(nèi)部的很多對象打交道
以下情況使用門面模式
- 為了使得子系統(tǒng)更具可復(fù)用性時,可以使用Facade模式為一個復(fù)雜子系統(tǒng)提供一個簡單接口
- 為了得到子系統(tǒng)獨立性和可移植性時,可以使用Facade模式將一個子系統(tǒng)與他的客戶端以及其他子系統(tǒng)分離
- 在構(gòu)建一個層次化的系統(tǒng)時,可以使用Facade模式定義系統(tǒng)中每一層的入口
創(chuàng)建網(wǎng)絡(luò)請求接口中方法的Call實例
代碼塊一
Call<List<Contributor>> call = github.contributors("square", "retrofit");
@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);
}
代理模式(本代碼塊為動態(tài)代理)--摘自《Java與模式》
代理主題并不改變主題的接口,因為模式的用意是不讓客戶端感覺到代理的存在
代理使用委派將客戶端的調(diào)用委派給真實的主題對象,換言之,代理主題起到的是一個傳遞請求的作用
代理主題在傳遞請求之前和之后都可以執(zhí)行特定的操作,而不是單純傳遞請求
以下情況使用代理模式
- 遠程代理:系統(tǒng)可以將網(wǎng)絡(luò)的細節(jié)隱藏起來,使得客戶端不必考慮網(wǎng)絡(luò)的存在
- 虛擬代理:代理對象可以在必要的時候才將被代理的對象加載
- 保護代理:在運行時間對用戶的相關(guān)權(quán)限進行檢查,然后在核實后決定是否將調(diào)用傳遞給被代理的對象
- 智能引用代理:在訪問一個對象時可以執(zhí)行一些內(nèi)務(wù)處理操作,比如計數(shù)或者日志操作
代碼塊二
public CallAdapter<?, ?> nextCallAdapter(@Nullable CallAdapter.Factory skipPast, Type returnType, Annotation[] annotations) {
int start = adapterFactories.indexOf(skipPast) + 1;
for (int i = start, count = adapterFactories.size(); i < count; i++) {
CallAdapter<?, ?> adapter = adapterFactories.get(i).get(returnType, annotations, this);
if (adapter != null) {
return adapter;
}
}
策略模式--摘自《Java與模式》
其是對算法的包裝,是把使用算法的責(zé)任和算法本身分隔開,委派給不同的對象管理。
其通常把一個系列的算法包裝到一系列的策略類里面,作為一個抽象策略類的子類。即,準備一組算法,并將每個算法封裝到具有共同接口的獨立的類中,使得它們可以互換。
其并不決定在何時使用何種算法,應(yīng)當由客戶端自己決定在什么情況下使用什么具體策略角色。
以下情況使用策略模式
- 一個系統(tǒng)中有許多類,而它們的區(qū)別僅在于它們的行為
- 一個系統(tǒng)需要動態(tài)的在幾種算法中選擇一種
- 一個系統(tǒng)的算法使用的數(shù)據(jù)不可以讓客戶端知道
- 一個對象有很多的行為,如果不用恰當?shù)哪J?,這些行為就只好使用多重的條件選擇語句來實現(xiàn),而此時可以考慮使用策略模式來解決。
代碼塊三
return new CallAdapter<Object, Call<?>>() {
@Override public Type responseType() {
return responseType;
}
@Override public Call<Object> adapt(Call<Object> call) {
return new ExecutorCallbackCall<>(callbackExecutor, call);
}
};
或
@Override public Object adapt(Call<R> call) {
OnSubscribe<Response<R>> callFunc = isAsync
? new CallEnqueueOnSubscribe<>(call)
: new CallExecuteOnSubscribe<>(call);
OnSubscribe<?> func;
if (isResult) {
func = new ResultOnSubscribe<>(callFunc);
} else if (isBody) {
func = new BodyOnSubscribe<>(callFunc);
} else {
func = callFunc;
}
Observable<?> observable = Observable.create(func);
if (scheduler != null) {
observable = observable.subscribeOn(scheduler);
}
if (isSingle) {
return observable.toSingle();
}
if (isCompletable) {
return observable.toCompletable();
}
return observable;
}
適配器模式--摘自《Java與模式》
把一個類的接口變換成客戶端所期待的另一種接口,從而使原本因接口不匹配而無法在一起工作的兩個類能夠在一起工作。
以下情況使用適配器模式
- 系統(tǒng)需要使用現(xiàn)有的類,而此類的接口不符合系統(tǒng)的需要
- 想要建立一個可以重復(fù)使用的類,用于與一些彼此之間沒有太大關(guān)聯(lián)的一些類(包括一些可能在將來引進的類)一起工作
發(fā)送網(wǎng)絡(luò)請求
static final class ExecutorCallbackCall<T> implements Call<T> {
final Executor callbackExecutor;
final Call<T> delegate; //注意,變量類型是父類的類型
ExecutorCallbackCall(Executor callbackExecutor, Call<T> delegate) {
this.callbackExecutor = callbackExecutor;
this.delegate = delegate;
}
@Override public void enqueue(final Callback<T> callback) {
checkNotNull(callback, "callback == null");
delegate.enqueue(new Callback<T>() {
@Override public void onResponse(Call<T> call, final Response<T> response) {
callbackExecutor.execute(new Runnable() {
@Override public void run() {
if (delegate.isCanceled()) {
// Emulate OkHttp's behavior of throwing/delivering an IOException on cancellation.
callback.onFailure(ExecutorCallbackCall.this, new IOException("Canceled"));
} else {
callback.onResponse(ExecutorCallbackCall.this, response);
}
}
});
}
@Override public void onFailure(Call<T> call, final Throwable t) {
callbackExecutor.execute(new Runnable() {
@Override public void run() {
callback.onFailure(ExecutorCallbackCall.this, t);
}
});
}
});
}
}
裝飾模式--摘自《Java與模式》
以對客戶透明的方式動態(tài)地給一個對象附加上更多的責(zé)任。換言之,客戶端并不會覺得對象在裝飾前和裝飾后有什么不同
可以在不使用創(chuàng)造更多子類的情況下,將對象的功能加以擴展,是繼承關(guān)系的一個替代方案
純粹的裝飾模式很難找到,裝飾模式用意在不改變接口的前提下,增強被裝飾類的性能,即要滿足透明性。在增強性能的時候,往往需要建立新的公開方法。這就導(dǎo)致了大多數(shù)的裝飾模式的實現(xiàn)都是“半透明”的。這意味著客戶端不去聲明抽象父類類型的變量,而是聲明具體裝飾類類型的變量,從而可以調(diào)用具體裝飾類中才有的方法。
以下情況使用裝飾模式
- 需要擴展一個類的功能,或給一個類增加附加責(zé)任
- 需要動態(tài)地給一個對象增加功能,這些功能可以再動態(tài)地撤銷
- 需要增加由一些基本功能的排列組合而產(chǎn)生的非常大量的功能,從而使繼承關(guān)系變得不現(xiàn)實
對于這一塊代碼,網(wǎng)絡(luò)上也有人將其歸類為代理模式,本獸下面說一下自己的觀點。
裝飾模式、適配器模式、代理模式都是包裝(Wrapper)模式,首先說一下他們之間的區(qū)別。
- 裝飾模式&適配器模式:適配器模式把一個API轉(zhuǎn)換成另一個API,而裝飾模式是保持被包裝的對象的API。用Java術(shù)語來講,適配器和被適配的類實現(xiàn)的是不同的接口和抽象類,而裝飾模式和被裝飾的類實現(xiàn)的是相同的接口和抽象類。半透明的裝飾模式實際上就是處于在適配器模式與裝飾模式之間的灰色地帶。
- 裝飾模式&代理模式:兩種模式相同點都是保持被包裝的對象的API。但是,裝飾模式為所裝飾的對象提供增強功能,而代理模式則為所代理對象的使用施加控制。
本獸認為,這一塊代碼,將其歸類為裝飾模式或者代理模式,都是可以的。因為設(shè)計模式本就是屬于概念性的,指導(dǎo)性的范疇,各個設(shè)計模式其實沒有那么清晰的界限,沒必要非要分個你清我楚,只要都滿足基本的設(shè)計原則(S.O.L.I.D)即可。
但如果非要較真(程序員的特點),到底偏向于裝飾模式還是偏向于代理模式,本獸認為其更偏向于裝飾模式,進一步來講,其更偏向于簡化后的裝飾模式。
裝飾模式簡易類圖如下:

簡化后的裝飾模式簡易類圖如下:

代理模式簡易類圖如下:

大家可以看到,簡化后的裝飾模式的簡易類圖和代理模式的簡易類圖,非常相像。相比較之后,不同點如下:
- 裝飾類含有的變量的類型,是父類的類型,而這個變量本身一般就是左側(cè)的被裝飾類的實例
- 代理類含有的變量的類型,直接就是左側(cè)被代理類的類型,從而這個變量本身必然是左側(cè)被代理類的實例
根據(jù)上述區(qū)別,再對應(yīng)到代碼塊,我們就可以得出結(jié)論,其更偏向于簡化后的裝飾模式。
處理返回數(shù)據(jù)
NA
總結(jié)
Retrofit更像是一個組織者,他把幾個框架高效的組合起來,在解耦的同時也滿足了擴展性:其利用OkHTTP進行網(wǎng)絡(luò)請求;與異步請求框架和類解析框架解耦,使得Retrofit可以適配多種框架,使用者可以輕松的選擇或者自創(chuàng)適合自己項目的異步請求和解析的框架。
讀者可以好好體會一下其是如何玩轉(zhuǎn)各種設(shè)計模式,把面向接口編程發(fā)揮得淋漓盡致的。
Retrofit 2.0之后網(wǎng)絡(luò)只支持OKHttp,對OKHttp強依賴。以后如果有新的網(wǎng)絡(luò)框架出現(xiàn),將無法使Retrofit。但是Retrofit提供的封裝網(wǎng)絡(luò)框架的思路依然值得借鑒。