面向接口編程
面向接口編程户魏,模塊化編程的必備技能,其乃實(shí)現(xiàn)解耦呻征,增強(qiáng)擴(kuò)展性的重要手段耘婚。
面向接口編程具體指的是什么呢?
首先說(shuō)一下什么是面向?qū)ο缶幊搪礁常蠹叶贾楞宓唬嫦驅(qū)ο缶幊淌窍鄬?duì)于面向過(guò)程編程來(lái)說(shuō)的,基本上攒岛,具有對(duì)象概念的程序設(shè)計(jì)都可以稱為面向?qū)ο缶幊汤盗佟6嫦蚪涌诰幊虄H僅是面向?qū)ο缶幊痰囊环N模塊化實(shí)現(xiàn)形式,其從組件的角度來(lái)進(jìn)行代碼設(shè)計(jì)灾锯,將抽象與實(shí)現(xiàn)分離兢榨。
接口泛指實(shí)體把自己提供給外界的一種抽象物,利用由內(nèi)部操作分離出的外部溝通方法顺饮,使得實(shí)體能被修改內(nèi)部而不影響外界其他實(shí)體與自己交互的方式色乾。面向接口編程中的“接口”,在Java語(yǔ)言中领突,不僅僅是“interface”關(guān)鍵字這么簡(jiǎn)單暖璧,interface、abstract class以及普通的class都能成為所謂的接口君旦。
interface約定的是務(wù)必實(shí)現(xiàn)的方法澎办,強(qiáng)調(diào)的是規(guī)則的制定嘲碱。abstract class則是在抽象的同時(shí)允許提供一些默認(rèn)的行為,以達(dá)到代碼復(fù)用的效果局蚀。一個(gè)實(shí)現(xiàn)類(相對(duì)于抽象而言)可以實(shí)現(xiàn)多個(gè)interface麦锯,而只能繼承一個(gè)abstract class。
面向接口編程能夠帶來(lái)什么呢琅绅?
面向接口編程可以降低代碼間的耦合扶欣,增強(qiáng)代碼的擴(kuò)展性,而正是這種特性千扶,使得多人同時(shí)開(kāi)發(fā)變成了可能料祠。其實(shí)大部分設(shè)計(jì)模式就是面向接口編程很好的例子。
面向接口編程如何實(shí)現(xiàn)呢澎羞?
進(jìn)行面向接口編程實(shí)操時(shí)髓绽,我們一般注意三點(diǎn):先定義接口(接口或者抽象類),再定義實(shí)現(xiàn)類妆绞;在函數(shù)間傳入傳出的是接口而不是實(shí)現(xiàn)類顺呕;一方只認(rèn)識(shí)另一方的接口,想進(jìn)入另一方括饶,就去調(diào)用另一方所披上的接口外套株茶。
從下圖中我們可以看到Retrofit用到了哪些接口類:
設(shè)計(jì)模式
我們從Retrofit的調(diào)用流程來(lái)分析用到的設(shè)計(jì)模式
創(chuàng)建Retrofit對(duì)象
Retrofit retrofit = new Retrofit.Builder()
.baseUrl(API_URL)
.addConverterFactory(GsonConverterFactory.create())
.build();
建造者模式--摘自《Java與模式》
建造者模式可以將一個(gè)產(chǎn)品的內(nèi)部表象與產(chǎn)品的生成過(guò)程分割開(kāi)來(lái),從而可以使一個(gè)建造過(guò)程生成具有不同的內(nèi)部表象的產(chǎn)品對(duì)象图焰。
在很多情況下启盛,建造者模式實(shí)際上是將一個(gè)對(duì)象的性質(zhì)建造過(guò)程外部化到獨(dú)立的建造者對(duì)象中,并通過(guò)一個(gè)導(dǎo)演者角色對(duì)這些外部化的性質(zhì)賦值過(guò)程進(jìn)行協(xié)調(diào)楞泼。
以下情況使用建造者模式
- 需要生成的產(chǎn)品對(duì)象有復(fù)雜的內(nèi)部結(jié)構(gòu)
- 需要生成的產(chǎn)品對(duì)象的屬性相互依賴
- 需要生成的產(chǎn)品對(duì)象有多個(gè)可選的構(gòu)造參數(shù)
- 防止生成的產(chǎn)品對(duì)象的參數(shù)被再次修改
創(chuàng)建網(wǎng)絡(luò)請(qǐng)求接口的實(shí)例
GitHub github = retrofit.create(GitHub.class);
門面模式--摘自《Java與模式》
門面模式要求一個(gè)子系統(tǒng)的外部與其內(nèi)部的通信必須通過(guò)一個(gè)統(tǒng)一的門面(Facade)對(duì)象進(jìn)行驰徊,門面提供一個(gè)高層次的接口笤闯,使得子系統(tǒng)更易于使用
門面模式的門面類將客戶端與子系統(tǒng)的內(nèi)部復(fù)雜性分隔開(kāi)堕阔,使得客戶端只需要與門面對(duì)象打交道,而不需要與子系統(tǒng)內(nèi)部的很多對(duì)象打交道
以下情況使用門面模式
- 為了使得子系統(tǒng)更具可復(fù)用性時(shí)颗味,可以使用Facade模式為一個(gè)復(fù)雜子系統(tǒng)提供一個(gè)簡(jiǎn)單接口
- 為了得到子系統(tǒng)獨(dú)立性和可移植性時(shí)超陆,可以使用Facade模式將一個(gè)子系統(tǒng)與他的客戶端以及其他子系統(tǒng)分離
- 在構(gòu)建一個(gè)層次化的系統(tǒng)時(shí),可以使用Facade模式定義系統(tǒng)中每一層的入口
創(chuàng)建網(wǎng)絡(luò)請(qǐng)求接口中方法的Call實(shí)例
代碼塊一
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);
}
代理模式(本代碼塊為動(dòng)態(tài)代理)--摘自《Java與模式》
代理主題并不改變主題的接口浦马,因?yàn)槟J降挠靡馐遣蛔尶蛻舳烁杏X(jué)到代理的存在
代理使用委派將客戶端的調(diào)用委派給真實(shí)的主題對(duì)象时呀,換言之,代理主題起到的是一個(gè)傳遞請(qǐng)求的作用
代理主題在傳遞請(qǐng)求之前和之后都可以執(zhí)行特定的操作晶默,而不是單純傳遞請(qǐng)求
以下情況使用代理模式
- 遠(yuǎn)程代理:系統(tǒng)可以將網(wǎng)絡(luò)的細(xì)節(jié)隱藏起來(lái)谨娜,使得客戶端不必考慮網(wǎng)絡(luò)的存在
- 虛擬代理:代理對(duì)象可以在必要的時(shí)候才將被代理的對(duì)象加載
- 保護(hù)代理:在運(yùn)行時(shí)間對(duì)用戶的相關(guān)權(quán)限進(jìn)行檢查,然后在核實(shí)后決定是否將調(diào)用傳遞給被代理的對(duì)象
- 智能引用代理:在訪問(wèn)一個(gè)對(duì)象時(shí)可以執(zhí)行一些內(nèi)務(wù)處理操作磺陡,比如計(jì)數(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與模式》
其是對(duì)算法的包裝趴梢,是把使用算法的責(zé)任和算法本身分隔開(kāi)漠畜,委派給不同的對(duì)象管理。
其通常把一個(gè)系列的算法包裝到一系列的策略類里面坞靶,作為一個(gè)抽象策略類的子類憔狞。即,準(zhǔn)備一組算法彰阴,并將每個(gè)算法封裝到具有共同接口的獨(dú)立的類中瘾敢,使得它們可以互換。
其并不決定在何時(shí)使用何種算法尿这,應(yīng)當(dāng)由客戶端自己決定在什么情況下使用什么具體策略角色簇抵。
以下情況使用策略模式
- 一個(gè)系統(tǒng)中有許多類,而它們的區(qū)別僅在于它們的行為
- 一個(gè)系統(tǒng)需要?jiǎng)討B(tài)的在幾種算法中選擇一種
- 一個(gè)系統(tǒng)的算法使用的數(shù)據(jù)不可以讓客戶端知道
- 一個(gè)對(duì)象有很多的行為妻味,如果不用恰當(dāng)?shù)哪J秸梗@些行為就只好使用多重的條件選擇語(yǔ)句來(lái)實(shí)現(xiàn),而此時(shí)可以考慮使用策略模式來(lái)解決责球。
代碼塊三
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與模式》
把一個(gè)類的接口變換成客戶端所期待的另一種接口焦履,從而使原本因接口不匹配而無(wú)法在一起工作的兩個(gè)類能夠在一起工作。
以下情況使用適配器模式
- 系統(tǒng)需要使用現(xiàn)有的類雏逾,而此類的接口不符合系統(tǒng)的需要
- 想要建立一個(gè)可以重復(fù)使用的類嘉裤,用于與一些彼此之間沒(méi)有太大關(guān)聯(lián)的一些類(包括一些可能在將來(lái)引進(jìn)的類)一起工作
發(fā)送網(wǎng)絡(luò)請(qǐng)求
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與模式》
以對(duì)客戶透明的方式動(dòng)態(tài)地給一個(gè)對(duì)象附加上更多的責(zé)任栖博。換言之屑宠,客戶端并不會(huì)覺(jué)得對(duì)象在裝飾前和裝飾后有什么不同
可以在不使用創(chuàng)造更多子類的情況下,將對(duì)象的功能加以擴(kuò)展仇让,是繼承關(guān)系的一個(gè)替代方案
純粹的裝飾模式很難找到典奉,裝飾模式用意在不改變接口的前提下,增強(qiáng)被裝飾類的性能丧叽,即要滿足透明性卫玖。在增強(qiáng)性能的時(shí)候,往往需要建立新的公開(kāi)方法踊淳。這就導(dǎo)致了大多數(shù)的裝飾模式的實(shí)現(xiàn)都是“半透明”的假瞬。這意味著客戶端不去聲明抽象父類類型的變量,而是聲明具體裝飾類類型的變量迂尝,從而可以調(diào)用具體裝飾類中才有的方法脱茉。
以下情況使用裝飾模式
- 需要擴(kuò)展一個(gè)類的功能,或給一個(gè)類增加附加責(zé)任
- 需要?jiǎng)討B(tài)地給一個(gè)對(duì)象增加功能垄开,這些功能可以再動(dòng)態(tài)地撤銷
- 需要增加由一些基本功能的排列組合而產(chǎn)生的非常大量的功能琴许,從而使繼承關(guān)系變得不現(xiàn)實(shí)
對(duì)于這一塊代碼,網(wǎng)絡(luò)上也有人將其歸類為代理模式溉躲,本獸下面說(shuō)一下自己的觀點(diǎn)榜田。
裝飾模式寸认、適配器模式、代理模式都是包裝(Wrapper)模式串慰,首先說(shuō)一下他們之間的區(qū)別偏塞。
- 裝飾模式&適配器模式:適配器模式把一個(gè)API轉(zhuǎn)換成另一個(gè)API,而裝飾模式是保持被包裝的對(duì)象的API邦鲫。用Java術(shù)語(yǔ)來(lái)講灸叼,適配器和被適配的類實(shí)現(xiàn)的是不同的接口和抽象類,而裝飾模式和被裝飾的類實(shí)現(xiàn)的是相同的接口和抽象類庆捺。半透明的裝飾模式實(shí)際上就是處于在適配器模式與裝飾模式之間的灰色地帶古今。
- 裝飾模式&代理模式:兩種模式相同點(diǎn)都是保持被包裝的對(duì)象的API。但是滔以,裝飾模式為所裝飾的對(duì)象提供增強(qiáng)功能捉腥,而代理模式則為所代理對(duì)象的使用施加控制。
本獸認(rèn)為你画,這一塊代碼抵碟,將其歸類為裝飾模式或者代理模式,都是可以的坏匪。因?yàn)樵O(shè)計(jì)模式本就是屬于概念性的拟逮,指導(dǎo)性的范疇,各個(gè)設(shè)計(jì)模式其實(shí)沒(méi)有那么清晰的界限适滓,沒(méi)必要非要分個(gè)你清我楚敦迄,只要都滿足基本的設(shè)計(jì)原則(S.O.L.I.D)即可。
但如果非要較真(程序員的特點(diǎn))凭迹,到底偏向于裝飾模式還是偏向于代理模式罚屋,本獸認(rèn)為其更偏向于裝飾模式,進(jìn)一步來(lái)講嗅绸,其更偏向于簡(jiǎn)化后的裝飾模式脾猛。
裝飾模式簡(jiǎn)易類圖如下:
簡(jiǎn)化后的裝飾模式簡(jiǎn)易類圖如下:
代理模式簡(jiǎn)易類圖如下:
大家可以看到,簡(jiǎn)化后的裝飾模式的簡(jiǎn)易類圖和代理模式的簡(jiǎn)易類圖朽砰,非常相像尖滚。相比較之后喉刘,不同點(diǎn)如下:
- 裝飾類含有的變量的類型瞧柔,是父類的類型,而這個(gè)變量本身一般就是左側(cè)的被裝飾類的實(shí)例
- 代理類含有的變量的類型睦裳,直接就是左側(cè)被代理類的類型造锅,從而這個(gè)變量本身必然是左側(cè)被代理類的實(shí)例
根據(jù)上述區(qū)別,再對(duì)應(yīng)到代碼塊廉邑,我們就可以得出結(jié)論哥蔚,其更偏向于簡(jiǎn)化后的裝飾模式倒谷。
處理返回?cái)?shù)據(jù)
NA
總結(jié)
Retrofit更像是一個(gè)組織者,他把幾個(gè)框架高效的組合起來(lái)糙箍,在解耦的同時(shí)也滿足了擴(kuò)展性:其利用OkHTTP進(jìn)行網(wǎng)絡(luò)請(qǐng)求渤愁;與異步請(qǐng)求框架和類解析框架解耦,使得Retrofit可以適配多種框架深夯,使用者可以輕松的選擇或者自創(chuàng)適合自己項(xiàng)目的異步請(qǐng)求和解析的框架抖格。
讀者可以好好體會(huì)一下其是如何玩轉(zhuǎn)各種設(shè)計(jì)模式,把面向接口編程發(fā)揮得淋漓盡致的咕晋。
Retrofit 2.0之后網(wǎng)絡(luò)只支持OKHttp雹拄,對(duì)OKHttp強(qiáng)依賴。以后如果有新的網(wǎng)絡(luò)框架出現(xiàn)掌呜,將無(wú)法使Retrofit滓玖。但是Retrofit提供的封裝網(wǎng)絡(luò)框架的思路依然值得借鑒。