本文將接著《Retrofit源碼設(shè)計(jì)模式解析(上)》蹂楣,繼續(xù)分享以下設(shè)計(jì)模式在Retrofit中的應(yīng)用:
- 適配器模式
- 策略模式
- 觀察者模式
- 單例模式
- 原型模式
- 享元模式
一赏参、適配器模式
在上篇說明CallAdapter.Factory
使用工廠模式時(shí)志笼,提到CallAdapter
本身采用了適配器模式。適配器模式將一個(gè)接口轉(zhuǎn)換成客戶端希望的另一個(gè)接口把篓,使接口本不兼容的類可以一起工作纫溃。
Call
接口是Retrofit
內(nèi)置的發(fā)送請(qǐng)求給服務(wù)器并且返回響應(yīng)體的調(diào)用接口,包括同步韧掩、異步請(qǐng)求紊浩,查詢、取消疗锐、復(fù)制等功能坊谁。
public interface Call<T> extends Cloneable {
// 同步執(zhí)行請(qǐng)求
Response<T> execute() throws IOException;
// 異步執(zhí)行請(qǐng)求
void enqueue(Callback<T> callback);
// 省略代碼
// 取消請(qǐng)求
void cancel();
// 復(fù)制請(qǐng)求
Call<T> clone();
}
而客戶端可能希望更適合業(yè)務(wù)邏輯的接口回調(diào),比如響應(yīng)式的接口回調(diào)滑臊。那么口芍,就需要對(duì)Call
進(jìn)行轉(zhuǎn)換,CallAdapter
就上場(chǎng)了雇卷。CallAdapter
包含兩個(gè)方法:
public interface CallAdapter<T> {
// 返回請(qǐng)求后,轉(zhuǎn)換的參數(shù)Type類型
Type responseType();
// 接口適配
<R> T adapt(Call<R> call);
}
如果客戶端沒有配置CallAdapter
鬓椭,Retrofit
會(huì)采用默認(rèn)的實(shí)現(xiàn)DefaultCallAdapterFactory
直接返回Call
對(duì)象,而如果配置了RxJava
的RxJavaCallAdapterFactory
實(shí)現(xiàn)关划,就會(huì)將Call<R>
轉(zhuǎn)換為Observable<R>
小染,供客戶端調(diào)用。
static final class SimpleCallAdapter implements CallAdapter<Observable<?>> {
// 省略代碼
@Override
public <R> Observable<R> adapt(Call<R> call) {
Observable<R> observable = Observable.create(new CallOnSubscribe<>(call))
.lift(OperatorMapResponseToBodyOrError.<R>instance());
if (scheduler != null) {
return observable.subscribeOn(scheduler);
}
return observable;
}
}
總結(jié)下贮折,適配器模式包含四種角色:
-
Target
:目標(biāo)抽象類 -
Adapter
:適配器類 -
Adaptee
:適配者類 -
Client
:客戶端類
CallAdapter
對(duì)應(yīng)Target
裤翩,其adapt
方法返回客戶端類Client
需要的對(duì)象;RxJavaCallAdapterFactory
的get
方法返回SimpleCallAdapter
對(duì)象(或ResultCallAdapter
對(duì)象)實(shí)現(xiàn)了CallAdapter<Observable<?>>
脱货,對(duì)應(yīng)Adapter
岛都;Call<R>
對(duì)應(yīng)Adaptee
適配者類,包含需要被適配的方法振峻。
另外臼疫,適配器模式有對(duì)象適配器和類適配器兩種實(shí)現(xiàn)。類適配器中的Adapter
需要繼承自Adaptee
扣孟,對(duì)象適配則是采用復(fù)合的方式烫堤,Adapter
持有Adaptee
的引用。類適配器模式會(huì)使Adaptee
的方法暴露給Adapter
,根據(jù)“復(fù)合優(yōu)先于繼承”的思想鸽斟,推薦使用對(duì)象適配器模式拔创。
值得說明的是,這里SimpleCallAdapter
并沒有通過域的方式持有Call<R>
富蓄,而是直接在CallAdapter
的get
方法中將Call<R>
以入?yún)⑿问絺魅胧T铩km然并不是教科書式的對(duì)象適配器模式,但使用卻更加靈活立倍、方便灭红。
二、策略模式
完成一項(xiàng)任務(wù)口注,往往可以有多種不同的方式变擒,每一種方式稱為一個(gè)策略,我們可以根據(jù)環(huán)境或者條件的不同選擇不同的策略來完成該項(xiàng)任務(wù)寝志。針對(duì)這種情況娇斑,一種常規(guī)的做法是將多個(gè)策略寫在一個(gè)類中,通過if…else或者switch等條件判斷語句來選擇具體的算法材部。這種方式實(shí)現(xiàn)簡(jiǎn)單毫缆、快捷,但維護(hù)成本很高败富,當(dāng)添加新的策略時(shí)悔醋,需要修改源代碼,這違背了開閉原則和單一原則兽叮。仍以CallAdapter
為例,不同的CallAdapter
代表著不同的策略猾愿,當(dāng)我們調(diào)用這些不同的適配器的方法時(shí)鹦聪,就能得到不同的結(jié)果,這就是策略模式蒂秘。策略模式包含三種角色:
-
Context
上下文環(huán)境——區(qū)別于Android
的Context
泽本,這里代表操作策略的上下文; -
Stragety
抽象策略——即不同策略需要實(shí)現(xiàn)的方法姻僧; -
ConcreteStragety
策略實(shí)現(xiàn)——實(shí)現(xiàn)Stragety
抽象策略规丽。
在Retrofit
中,配置Retrofit.Builder
時(shí)addCallAdapterFactory撇贺,配置的類就對(duì)應(yīng)Context
赌莺;不同的CallAdapter
都需要提供adapt
方法,CallAdapter<T>
就對(duì)應(yīng)Stragety
抽象策略松嘶。RxJavaCallAdapterFactory
的get
方法返回SimpleCallAdapte
r對(duì)象(或ResultCallAdapter
對(duì)象)就對(duì)應(yīng)具體的策略實(shí)現(xiàn)艘狭。
這里可能會(huì)跟上篇中的工廠模式搞混,在說明工廠模式時(shí),主要是強(qiáng)調(diào)的是:
public abstract CallAdapter<?> get(Type returnType, Annotation[] annotations, Retrofit retrofit);
通過get
方法返回不同的CallAdapter
對(duì)象巢音;策略模式強(qiáng)調(diào)的是這些不同CallAdapter
對(duì)象的adapt
方法的具體實(shí)現(xiàn)遵倦。
<R> T adapt(Call<R> call);
總結(jié)下:工廠模式強(qiáng)調(diào)的是生產(chǎn)不同的對(duì)象,策略模式強(qiáng)調(diào)的是這些不同對(duì)象的策略方法的具體實(shí)現(xiàn)官撼,是在創(chuàng)建對(duì)象之后梧躺。
三、觀察者模式
建立一種對(duì)象與對(duì)象之間的依賴關(guān)系傲绣,一個(gè)對(duì)象發(fā)生改變時(shí)將自動(dòng)通知其他對(duì)象燥狰,其他對(duì)象將相應(yīng)做出反應(yīng)。在此斜筐,發(fā)生改變的對(duì)象稱為觀察目標(biāo)龙致,而被通知的對(duì)象稱為觀察者,一個(gè)觀察目標(biāo)可以對(duì)應(yīng)多個(gè)觀察者顷链,而且這些觀察者之間沒有相互聯(lián)系目代,可以根據(jù)需要增加和刪除觀察者,使得系統(tǒng)更易于擴(kuò)展嗤练,這就是觀察者模式的模式動(dòng)機(jī)榛了。
舉個(gè)栗子:在Android
編程中,常見的一種情況是界面上某個(gè)控件的狀態(tài)對(duì)其它控件有約束關(guān)系煞抬,比如霜大,需要根據(jù)某個(gè)EditText
的輸入值決定某個(gè)按鈕是否可以點(diǎn)擊,就需要此EditText
是可觀測(cè)的對(duì)象革答,而按鈕是EditText
的觀測(cè)者战坤,當(dāng)EditText
狀態(tài)發(fā)生改變時(shí),按鈕進(jìn)行相應(yīng)的操作残拐。
觀察者模式包含四種角色:
-
Subject
抽象主題——也就是被觀察對(duì)象途茫,Observable
是JDK中內(nèi)置的類(java.util.Observable
),當(dāng)需要定義被觀察對(duì)象時(shí)溪食,繼承自Observable
即可囊卜; -
ConcreteSubject
具體主題——具體被觀察者,可以繼承Observable
實(shí)現(xiàn)错沃,需要通知觀察者時(shí)栅组,調(diào)用notifyObservers
; -
Observer
抽象觀察者——Observer
也是JDK內(nèi)置的枢析,定義了update
方法玉掸; -
ConcreteObserve
r具體觀察者——實(shí)現(xiàn)Observer
接口定義的update
方法,以便在狀態(tài)發(fā)生變化時(shí)更新自己登疗。
public interface Observer {
void update(Observable observable, Object data);
}
public class Observable {
List<Observer> observers = new ArrayList<Observer>();
// 省略代碼
public void notifyObservers(Object data) {
int size = 0;
Observer[] arrays = null;
synchronized (this) {
if (hasChanged()) {
clearChanged();
size = observers.size();
arrays = new Observer[size];
observers.toArray(arrays);
}
}
if (arrays != null) {
for (Observer observer : arrays) {
observer.update(this, data);
}
}
}
}
所有與網(wǎng)絡(luò)請(qǐng)求相關(guān)的庫(kù)一定會(huì)支持請(qǐng)求的異步發(fā)送排截,通過在庫(kù)內(nèi)部維護(hù)一個(gè)隊(duì)列嫌蚤,將請(qǐng)求添加到該隊(duì)列,同時(shí)注冊(cè)一個(gè)回調(diào)接口断傲,以便執(zhí)行引擎完成該請(qǐng)求后脱吱,將請(qǐng)求結(jié)果進(jìn)行回調(diào)。Retrofit
也不例外认罩,Retrofit
的網(wǎng)絡(luò)請(qǐng)求執(zhí)行引擎是OkHttp
箱蝠,請(qǐng)求類是OkHttpCall
,其實(shí)現(xiàn)了Call
接口垦垂,enqueue
方法如下宦搬,入?yún)?code>Callback對(duì)象。
void enqueue(Callback<T> callback);
在OkHttpCall
的enqueue
實(shí)現(xiàn)方法中劫拗,通過在okhttp3.Callback()
的回調(diào)方法中調(diào)用上述入?yún)?code>Callback對(duì)象的方法间校,實(shí)現(xiàn)通知觀察者。
@Override
public void enqueue(final Callback<T> callback) {
// 省略代碼
call.enqueue(new okhttp3.Callback() {
@Override
public void onResponse(okhttp3.Call call, okhttp3.Response rawResponse)
throws IOException {
Response<T> response;
try {
response = parseResponse(rawResponse);
} catch (Throwable e) {
callFailure(e);
return;
}
callSuccess(response);
}
@Override
public void onFailure(okhttp3.Call call, IOException e) {
try {
callback.onFailure(OkHttpCall.this, e);
} catch (Throwable t) {
t.printStackTrace();
}
}
private void callSuccess(Response<T> response) {
try {
callback.onResponse(OkHttpCall.this, response);
} catch (Throwable t) {
t.printStackTrace();
}
}
總結(jié)下:Call
接口對(duì)應(yīng)Subject
页慷,定義被觀察者的特性憔足,包含enqueue
等;OkHttpCall
對(duì)應(yīng)ConcreteSubject
具體被觀察者酒繁,Callback
對(duì)應(yīng)Observer
抽象觀察者滓彰,Callback
的實(shí)現(xiàn)類對(duì)應(yīng)ConcreteObserver
具體觀察者。
四州袒、單例模式
單例模式可能是所有設(shè)計(jì)模式教程的第一個(gè)講到的模式揭绑,也是應(yīng)用最廣泛的模式之一。Retrofit
中也使用了大量的單例模式郎哭,比如BuiltInConverters
的responseBodyConverter
他匪、requestBodyConverter
等,并且使用了餓漢式的單例模式彰居。由于這種單例模式應(yīng)用最廣诚纸,也是大家都清楚的,本節(jié)將擴(kuò)展下單例模式的其它實(shí)現(xiàn)方式陈惰。
** 懶漢式單例模式:**
public class Singleton {
private static Singleton instance;
private Singleton() {
}
public static synchronized Singleton getInstance() {
if (instance == null) {
instance = new Singleton();
}
return instance;
}
}
懶漢單例模式的優(yōu)點(diǎn)是單例只要有在使用是才被實(shí)例化,缺點(diǎn)是每次調(diào)用getInstance
都進(jìn)行同步毕籽,造成不必要的同步開銷抬闯。
** DCL(Double Check Lock):**
public class Singleton {
private static Singleton instance;
private Singleton() {
}
public static Singleton getInstance() {
if (instance == null) {
synchronized (Singleton.class) {
if (instance == null) {
instance = new Singleton();
}
}
}
return instance;
}
}
DCL是對(duì)懶漢單例模式的升級(jí),getInstance
方法對(duì)instance
進(jìn)行了兩次判空关筒,第一層判斷是為了避免不必要的同步溶握,第二層判斷是為了在null時(shí)創(chuàng)建實(shí)例,這里涉及到對(duì)象實(shí)例化過程的原子問題蒸播。在Java中睡榆,創(chuàng)建對(duì)象并非原子操作萍肆,而是包含分配內(nèi)存、初始化成員字段胀屿、引用指向等一連串操作塘揣,而多線程環(huán)境下,由于指令重排序的存在宿崭,初始化指令和引用指令可能是顛倒亲铡,那么可能當(dāng)線程執(zhí)行第一個(gè)判斷不為null返回的對(duì)象,卻是未經(jīng)初始化的(別的對(duì)象創(chuàng)建Singleton時(shí)葡兑,初始化指令和引用指令顛倒了)奖蔓。
** 靜態(tài)內(nèi)部類: **
public class Singleton {
private Singleton() {
}
public static Singleton getInstance() {
return SingletonHolder.instance;
}
private static class SingletonHolder {
private static final Singleton instance = new Singleton();
}
}
上述DCL也是可能失效的,具體可參考《有關(guān)“雙重檢查鎖定失效”的說明》讹堤。采用靜態(tài)內(nèi)部類吆鹤,加載Singleton
類時(shí)并不會(huì)初始化instance
,同時(shí)也能保證線程安全洲守,單例對(duì)象的唯一性疑务。
** 枚舉單例:**
public enum Singleton {
INSTANCE;
}
枚舉實(shí)例的創(chuàng)建默認(rèn)是線程安全的,并且在任何情況下都只有一個(gè)實(shí)例岖沛。上述單例模式存在反序列化會(huì)重新創(chuàng)建對(duì)象的情況暑始,而枚舉不存在這個(gè)問題。但Android編程中婴削,因?yàn)樾阅軉栴}廊镜,不推薦使用枚舉,所以唉俗,這種比較怪異的方式并不推薦嗤朴。
** 使用容器實(shí)現(xiàn)單例模式:**
public class Singleton {
private static Map<String, Object> objectMap = new HashMap<>();
public static void addObject(String key, Object instance) {
if (!objectMap.containsKey(key)) {
objectMap.put(key, instance);
}
}
public static Object getObject(String key) {
return objectMap.get(key);
}
}
嚴(yán)格的講,這并不是標(biāo)準(zhǔn)的單例模式虫溜,但確實(shí)實(shí)現(xiàn)了單例的效果雹姊。
單例的核心原理是將構(gòu)造函數(shù)私有化,通過靜態(tài)方法獲取唯一實(shí)例衡楞。而怎么獲取唯一實(shí)例吱雏?在Java中可能存在線程安全、反序列化等問題瘾境,因此衍生出上述這幾個(gè)版本歧杏。在實(shí)際使用時(shí)需要根據(jù)并發(fā)環(huán)境、JDK版本以及資源消耗等因素綜合考慮迷守。
五犬绒、原型模式
原型模式是一種創(chuàng)建型模式,主要用于對(duì)象復(fù)制兑凿。使用原型模式創(chuàng)建對(duì)象比直接new一個(gè)對(duì)象在性能上要好的多凯力,因?yàn)?code>Object類的clone
方法是一個(gè)本地方法茵瘾,它直接操作內(nèi)存中的二進(jìn)制流。使用原型模式的另一個(gè)好處是簡(jiǎn)化對(duì)象的創(chuàng)建咐鹤,使得創(chuàng)建對(duì)象就像在編輯文檔時(shí)的復(fù)制粘貼拗秘。基于以上優(yōu)點(diǎn)慷暂,在需要重復(fù)地創(chuàng)建相似對(duì)象時(shí)可以考慮使用原型模式聘殖。比如需要在一個(gè)循環(huán)體內(nèi)創(chuàng)建對(duì)象,假如對(duì)象創(chuàng)建過程比較復(fù)雜或者循環(huán)次數(shù)很多的話行瑞,使用原型模式不但可以簡(jiǎn)化創(chuàng)建過程奸腺,而且可以使系統(tǒng)的整體性能提高很多。
原型模式有三種角色:
-
Client
客戶端血久; -
Prototype
原型——一般表現(xiàn)為抽象類或者接口突照,比如JDK中的Cloneable
接口; -
ConcretePrototype
具體原型類——實(shí)現(xiàn)了Prototype
原型氧吐。
OkHttpCall
實(shí)現(xiàn)了Call
接口讹蘑,Call
接口繼承自Cloneable
,OkHttpCall
的clone
方法實(shí)現(xiàn)如下:
@Override
public OkHttpCall<T> clone() {
return new OkHttpCall<>(serviceMethod, args);
}
clone
的實(shí)現(xiàn)就是重新new了一個(gè)一樣的對(duì)象筑舅,用于其他地方重用相同的Call
座慰,在ExecutorCallbackCall
中有用到:
static final class ExecutorCallbackCall<T> implements Call<T> {
// 省略代碼
@SuppressWarnings("CloneDoesntCallSuperClone") // Performing deep clone.
@Override
public Call<T> clone() {
return new ExecutorCallbackCall<>(callbackExecutor, delegate.clone());
}
}
使用原型模式復(fù)制對(duì)象需要主要深拷貝與淺拷貝的問題。Object
類的clone
方法只會(huì)拷貝對(duì)象中的基本的數(shù)據(jù)類型翠拣,對(duì)于數(shù)組版仔、容器對(duì)象、引用對(duì)象等都不會(huì)拷貝误墓,這就是淺拷貝蛮粮。如果要實(shí)現(xiàn)深拷貝,必須將原型模式中的數(shù)組谜慌、容器對(duì)象然想、引用對(duì)象等另行拷貝。
六欣范、享元模式
享元模式是對(duì)象池的一種實(shí)現(xiàn)变泄,運(yùn)用共享技術(shù)有效地支持大量細(xì)粒度對(duì)象的復(fù)用。系統(tǒng)只使用少量的對(duì)象恼琼,而這些對(duì)象都很相似杖刷,狀態(tài)變化很小,可以實(shí)現(xiàn)對(duì)象的多次復(fù)用驳癌。由于享元模式要求能夠共享的對(duì)象必須是細(xì)粒度對(duì)象,因此它又稱為輕量級(jí)模式(Flyweight
)役听,它是一種對(duì)象結(jié)構(gòu)型模式颓鲜。
享元模式包含三種角色:
-
Flyweight
享元基類或接口表窘; -
ConcreteFlyweight
具體的享元對(duì)象; -
FlyweightFactory
享元工廠——負(fù)責(zé)管理享元對(duì)象池和創(chuàng)建享元對(duì)象甜滨。
Retrofit
中create
方法創(chuàng)建ServiceMethod
是通過loadServiceMethod
方法實(shí)現(xiàn)乐严。loadServiceMethod
方法就實(shí)現(xiàn)了享元模式。
private final Map<Method, ServiceMethod> serviceMethodCache = new LinkedHashMap<>();
ServiceMethod loadServiceMethod(Method method) {
ServiceMethod result;
synchronized (serviceMethodCache) {
result = serviceMethodCache.get(method);
if (result == null) {
result = new ServiceMethod.Builder(this, method).build();
serviceMethodCache.put(method, result);
}
}
return result;
}
上篇講到代理模式的時(shí)候衣摩,提到了這個(gè)方法的緩存使用了LinkedHashMap
昂验,系統(tǒng)中的Method接口數(shù)相對(duì)于請(qǐng)求次數(shù)是有數(shù)量級(jí)差距的,把這些接口的信息緩存起來是非常有必要的一個(gè)優(yōu)化手段艾扮,這樣的實(shí)現(xiàn)方式就是享元模式既琴。
在享元模式中共享的是享元對(duì)象的內(nèi)部狀態(tài),外部狀態(tài)需要通過環(huán)境來設(shè)置泡嘴。在實(shí)際使用中甫恩,能夠共享的內(nèi)部狀態(tài)是有限的,因此享元對(duì)象一般都設(shè)計(jì)為較小的對(duì)象酌予,它所包含的內(nèi)部狀態(tài)較少磺箕,這種對(duì)象也稱為細(xì)粒度對(duì)象。享元模式的目的就是使用共享技術(shù)來實(shí)現(xiàn)大量細(xì)粒度對(duì)象的復(fù)用抛虫。在經(jīng)典享元模式中松靡,它的鍵是享元對(duì)象的內(nèi)部狀態(tài),它的值就是享元對(duì)象本身建椰。上述serviceMethodCache
的key
是method
雕欺,value
是ServiceMethod
,method
就是ServiceMethod
的內(nèi)部狀態(tài)广凸。
總結(jié):Retrofit
不愧是大師之作阅茶,設(shè)計(jì)模式的經(jīng)典教程。其源碼量并不大谅海,但系統(tǒng)的可擴(kuò)展性脸哀、可維護(hù)性極強(qiáng),是客戶端架構(gòu)設(shè)計(jì)的典范扭吁,非常值得學(xué)習(xí)撞蜂,五星推薦!