本篇文章已授權為微信公眾號 code小生
發(fā)布轉載請注明出處:http://www.reibang.com/p/dab7f5720aa5
引言
在上一篇 品讀Retrofit設計之美后汪疮,我們了解了Builder構建者模式和(動態(tài))代理模式在Retrofit中的做用,以及它們的使用套路楼眷。今天繼續(xù)品讀Retrofit框架值得我們好好思考的設計:抽象工廠模式
抽象工廠模式
在看Retrofit的抽象工廠模式的應用前,先來了解下,抽象工廠模式的套路膝擂,不扯虛的直接舉一個實用的例子:
我們都知道作為app開發(fā)者,通常的app應用都會有用戶系統(tǒng)拂募,一個用戶系統(tǒng)往往都包含了以下模塊:1. 登錄模塊庭猩。 2. 注冊模塊。 3. 找回密碼模塊陈症。 4. 用戶個人信息模塊蔼水。
這幾個模塊代表工廠需要生產(chǎn)的不同類型的產(chǎn)品,用戶系統(tǒng)帳號录肯,我們可能是app自身的帳號趴腋、密碼、或者手機短信驗證碼的登錄方式嘁信,也可能是第三方平臺帳號登錄:微信于样、QQ、新浪微博等等潘靖。對于不同的平臺的用戶帳號我們可以看做不同的品牌工廠穿剖,比如:app自身的用戶帳號工廠、微信的用戶帳號工廠卦溢、QQ的用戶帳號工廠糊余、新浪微博的用戶帳號工廠。
這樣來設計一個用戶系統(tǒng)是不是更清晰點单寂,而且不同的品牌的工廠便于替換贬芥,也就是替換登錄的平臺,不同的產(chǎn)品模塊類的功能職責也變的比較單一符合設計模式的單一原則宣决。
案例實現(xiàn)
- 首先抽象出各個產(chǎn)品接口出來蘸劈,每種模塊產(chǎn)品都有各自的功能
// ******************IBaseUser.java,抽象用戶實體
/**
* 抽象用戶實體接口尊沸,便于泛型化設計
*/
public interface IBaseUser {
}
// 1. ******************ILoginer.java威沫,登錄模塊
/**
* 登錄抽象接口
* @param <U> 用戶信息
*/
public interface ILoginer<U extends IBaseUser> {
// 登錄
void login(U user);
// 注銷贤惯、退出帳號
void logout(U user);
}
// 2. ******************IRegister.java,注冊模塊
/**
* 注冊帳號接口
* @param <U> 用戶信息
*/
public interface IRegister<U extends IBaseUser> {
// 注冊帳號
void registerAccount(U user);
}
// 3. ******************IFindPwder.java棒掠,找回密碼模塊
/**
* 找回密碼接口
* @param <U> 用戶信息
*/
public interface IFindPwder<U extends IBaseUser> {
// 找回密碼
void findPwd(U user);
}
// 4. ******************IUserInfoer.java孵构,用戶信息模塊
/**
* 用戶信息相關接口
* @param <U> 用戶信息
*/
public interface IUserInfoer<U extends IBaseUser> {
// 獲取用戶信息
U getUserInfo();
// 保存用戶信息
void saveUserInfo(U userInfo);
}
這些產(chǎn)品模塊的接口規(guī)范功能抽象,對于app的用戶系統(tǒng)來說基本夠用了烟很。當然上面的這些接口颈墅,也可以統(tǒng)一用一個接口文件來寫,這些模塊就作為子接口嵌套在里面雾袱,這是為了方便管理恤筛。
- 然后是工廠的抽象接口,用于生產(chǎn)不同品牌的不同產(chǎn)品
// ******************IUserSystemFactory .java谜酒,抽象的工廠接口
/**
* 用戶系統(tǒng)抽象工廠:登錄叹俏、注冊、找回密碼僻族、用戶信息等模塊
*/
public interface IUserSystemFactory {
// 獲取登錄模塊粘驰,登錄器
ILoginer getLoginer();
// 獲取注冊模塊,注冊器
IRegister getRegister();
// 找回密碼模塊
IFindPwder getFindPwder();
// 用戶信息模塊
IUserInfoer getUserInfoer();
}
主要就是獲取不同模塊的產(chǎn)品抽象接口對象述么,便于客戶端使用工廠的模塊對象的時候多態(tài)性蝌数。
- 實現(xiàn)不同登錄方式的工廠和具體的用戶系統(tǒng)模塊
因為用戶系統(tǒng)大部分情況下都需要和UI交互,所以封裝了一層基類把Context上下文統(tǒng)一起來度秘,減少子類的不必要的重復顶伞。
// *************BaseLoginer.java
/**
* 登錄模塊的基類
* @param <U> 用戶信息
*/
public abstract class BaseLoginer<U extends IBaseUser> implements ILoginer<U> {
private Context mContext;
public BaseLoginer(Context context) {
this.mContext = context;
}
}
// *************BaseUserSystemFactory.java
/**
* 用戶系統(tǒng)工廠基類
*/
public abstract class BaseUserSystemFactory implements IUserSystemFactory {
private Context mContext;
public BaseUserSystemFactory(Context context) {
this.mContext = context;
}
// 工廠對象可以獲取上下文
public Context getContext(){
return mContext;
}
}
比如,當我們使用app自己的用戶帳號登錄的時候的實現(xiàn)
// ******************SystemAccountLoginer.java
/**
* 使用應用帳號登錄
*/
public class SystemAccountLoginer extends BaseLoginer<User> {
public SystemAccountLoginer(Context context) {
super(context);
}
@Override
public void login(User user) {
// 登錄app
}
@Override
public void logout(User user) {
// 注銷退出帳號
}
}
// ******************SystemAccountFactory.java
/**
* 系統(tǒng)帳號登錄時的用戶系統(tǒng)工廠
*/
public class SystemAccountFactory extends BaseUserSystemFactory {
private SystemAccountFactory(Context context) {
super(context);
}
public static IUserSystemFactory create(Context context){
return new SystemAccountFactory(context);
}
@Override
public ILoginer getLoginer() {
// 返回對應的登錄產(chǎn)品(app自己的帳號平臺登錄對象)
return new SystemAccountLoginer(getContext());
}
@Override
public IRegister getRegister() {
// 返回對應的注冊產(chǎn)品(app自己的帳號平臺注冊對象)
return null;
}
@Override
public IFindPwder getFindPwder() {
// 返回對應的找回密碼產(chǎn)品(app自己的帳號平臺找回密碼對象)
return null;
}
@Override
public IUserInfoer getUserInfoer() {
// 返回對應的用戶信息產(chǎn)品(app自己的帳號平臺用戶信息對象)
return null;
}
}
再比如剑梳,用微信來登錄應用
// ******************WeixinLoginer.java
/**
* 使用微信登錄
*/
public class WeixinLoginer extends BaseLoginer<User> {
public WeixinLoginer(Context context) {
super(context);
}
@Override
public void login(User user) {
// 使用微信登錄
}
@Override
public void logout(User user) {
// 退出登錄
}
}
// ******************WeixinFactory.java
/**
* 系統(tǒng)帳號登錄時的用戶系統(tǒng)工廠
*/
public class WeixinFactory extends BaseUserSystemFactory {
private WeixinFactory(Context context) {
super(context);
}
public static IUserSystemFactory create(Context context){
return new WeixinFactory(context);
}
@Override
public ILoginer getLoginer() {
return new WeixinLoginer(getContext());
}
@Override
public IRegister getRegister() {
return null;
}
@Override
public IFindPwder getFindPwder() {
return null;
}
@Override
public IUserInfoer getUserInfoer() {
return null;
}
}
這里我實現(xiàn)了登錄產(chǎn)品模塊的唆貌,其它的模塊也是一樣的。對于調(diào)用者的使用也很簡單:
// 客戶端調(diào)用
// 使用自己的帳號平臺
IUserSystemFactory factory = SystemAccountFactory.create(this);
// 使用微信平臺帳號
// IUserSystemFactory weixinFactory = WeixinFactory.create(this);
User user = new User();
user.setUserId("1256339899879");
user.setPhone("13888888888");
// 使用自己的帳號登錄app
factory.getLoginer().login(user);
// 使用自己的帳號注冊
factory.getRegister().registerAccount(user);
// 使用找回自己帳號的密碼
factory.getFindPwder().findPwd(user);
// 獲取用戶信息
factory.getUserInfoer().getUserInfo();
對于調(diào)用者來說很簡單垢乙,只要關心當前用的是什么平臺的帳號系統(tǒng)锨咙,而不需要關心具體的實現(xiàn)方式。也把不同平臺的登錄追逮、注冊酪刀、獲取用戶信息等分離開來。當然往往不同的平臺可能退出當前帳號的方式是一樣钮孵,這個時候骂倘,其實可以把BaseLoginer當做代理對象,目標接口就是ILoginer巴席,目標對象另外新建一個類實現(xiàn)目標接口历涝,利用代理模式。
Retrofit抽象工廠的應用
我們都知道網(wǎng)絡請求通訊,當服務端返回數(shù)據(jù)后荧库,都需要進行解析轉換為可以直接使用的實體對象诱担,便于設置顯示到UI界面上,我們在構建Retrofit對象的時候往往會給構建器注入一個解析轉換器工廠對象电爹。
new Retrofit.Builder()
.baseUrl(AppConst.BASE_URL)
.client(buildHttpClient())
.addConverterFactory(FastJsonConverterFactory.create())
.build();
其中FastJsonConverterFactory.create()創(chuàng)建的就是一個Factory抽象工廠對象。
// 數(shù)據(jù)轉換器抽象產(chǎn)品類
// F是入?yún)⒘暇Γ琓是出參(轉換后的數(shù)據(jù)類型)
public interface Converter<F, T> {
// 產(chǎn)品的轉換操作
T convert(F value) throws IOException;
// 抽象工廠類
abstract class Factory {
// 工廠生產(chǎn)的請求響應的轉換器產(chǎn)品
public Converter<ResponseBody, ?> responseBodyConverter(Type type, Annotation[] annotations,
Retrofit retrofit) {
return null;
}
// 工廠生產(chǎn)的請求發(fā)起的轉換器產(chǎn)品
public Converter<?, RequestBody> requestBodyConverter(Type type,
Annotation[] parameterAnnotations, Annotation[] methodAnnotations, Retrofit retrofit) {
return null;
}
// 工廠生產(chǎn)的用于轉換字符串數(shù)據(jù)類型的轉換器產(chǎn)品
public Converter<?, String> stringConverter(Type type, Annotation[] annotations,
Retrofit retrofit) {
return null;
}
}
}
接下來看看使用FastJson作為轉換器的工廠實現(xiàn)類:
public class FastJsonConverterFactory extends Converter.Factory {
// 創(chuàng)建工廠對象
public static FastJsonConverterFactory create() {
return new FastJsonConverterFactory();
}
private FastJsonConverterFactory() {
}
@Override
public Converter<ResponseBody, ?> responseBodyConverter(Type type, Annotation[] annotations,
Retrofit retrofit) {
return new FastJsonResponseBodyConverter<>(type, mParserConfig, featureValues, features);
}
@Override
public Converter<?, RequestBody> requestBodyConverter(Type type,
Annotation[] parameterAnnotations, Annotation[] methodAnnotations, Retrofit retrofit) {
return new FastJsonRequestBodyConverter<>(serializeConfig, serializerFeatures);
}
}
通過封裝一個create方法丐箩,來創(chuàng)建工廠對象,外部調(diào)用者就不需要關系工廠對象是如何創(chuàng)建的恤煞。這點和我上面舉的例子是一樣的屎勘。再一個通過responseBodyConverter、requestBodyConverter方法分別創(chuàng)建了請求響應和請求發(fā)起這兩種產(chǎn)品的對象居扒。
再來看看FastJsonRequestBodyConverter請求發(fā)起轉換產(chǎn)品的實現(xiàn):
// 實現(xiàn)了轉換器這抽象產(chǎn)品類概漱,入?yún)⑹荝equestBody,返回的結果是泛型T
final class FastJsonRequestBodyConverter<T> implements Converter<T, RequestBody> {
private static final MediaType MEDIA_TYPE = MediaType.parse("application/json; charset=UTF-8");
private SerializeConfig serializeConfig;
private SerializerFeature[] serializerFeatures;
FastJsonRequestBodyConverter(SerializeConfig config, SerializerFeature... features) {
serializeConfig = config;
serializerFeatures = features;
}
@Override
public RequestBody convert(T value) throws IOException {
byte[] content;
if (serializeConfig != null) {
if (serializerFeatures != null) {
content = JSON.toJSONBytes(value, serializeConfig, serializerFeatures);
} else {
content = JSON.toJSONBytes(value, serializeConfig);
}
} else {
if (serializerFeatures != null) {
content = JSON.toJSONBytes(value, serializerFeatures);
} else {
content = JSON.toJSONBytes(value);
}
}
return RequestBody.create(MEDIA_TYPE, content);
}
}
實現(xiàn)了轉換器這抽象產(chǎn)品接口類喜喂,入?yún)⑹荝equestBody瓤摧,返回的結果是泛型T(因為請求的參數(shù)是針對具體業(yè)務的作為框架無法確定,于是用泛型來代替)玉吁,這個FastJsonRequestBodyConverter產(chǎn)品的功能就是convert轉換功能照弥,這里使用了阿里巴巴的json解析庫fastJson來轉換,具體的實現(xiàn)就是通過JSON.toJSONBytes方法轉換出json的字節(jié)數(shù)組进副,然后交由給OkHttp的RequestBody.create來構建一個請求體这揣,并且請求的多媒體類型是json格式的。OkHttp中的實現(xiàn):
public static RequestBody create(final MediaType contentType, final byte[] content,
final int offset, final int byteCount) {
if (content == null) throw new NullPointerException("content == null");
Util.checkOffsetAndCount(content.length, offset, byteCount);
return new RequestBody() {
@Override public MediaType contentType() {
return contentType;
}
@Override public long contentLength() {
return byteCount;
}
@Override public void writeTo(BufferedSink sink) throws IOException {
// content請求的參數(shù)內(nèi)容都通過Okio的BufferedSink來寫入了
sink.write(content, offset, byteCount);
}
};
}
你會發(fā)現(xiàn)RequestBody是個抽象類影斑,writeTo是個抽象方法给赞,那么必定就有調(diào)用此方法的地方。也不能盲目的看源碼找矫户,一個請求的構建最好的地方就是發(fā)起請求的時候片迅,call.enqueue(callback),通過enqueue發(fā)起一個異步的請求吏垮,但Call是接口障涯,也不曉得實現(xiàn)類。還有個辦法就是倒退的方式膳汪,將光標放置上門的writeTo方法上唯蝶,按組合鍵(有使用到writeTo的地方):ctrl + alt + F7:
很明顯是最后一個ReqeustBuilder,請求構建類遗嗽,跟進去是ContentTypeOverridingRequestBody粘我,它是個代理類,目標對象是其內(nèi)部的RequestBody對象這個對象我們猜測就是上文FastJsonRequestBodyConverter的converter轉換創(chuàng)建的RequestBody。再來看看ContentTypeOverridingRequestBody在RequestBuild的build()構建方法中有使用:
// 很明顯因為請求對象初始化比較復雜征字,就通過構建者模式構建了一個OkHttp的Request對象
class RequestBuild{
Request build() {
// 很明顯我們在構建Retrofit的時候有傳入FastJson的請求發(fā)起產(chǎn)品的生成工廠對象都弹,因此姑且任務body是有值的不等于null
RequestBody body = this.body;
if (body == null) {
// Try to pull from one of the builders.
if (formBuilder != null) {
body = formBuilder.build();
} else if (multipartBuilder != null) {
body = multipartBuilder.build();
} else if (hasBody) {
// Body is absent, make an empty body.
body = RequestBody.create(null, new byte[0]);
}
}
// 這里給body做了一層代理,實際的目標接口還是之前FastJsonRequestBodyConverter創(chuàng)建的body目標對象自己來調(diào)用的
// 而后把代理對象body給了Request進行構建請求發(fā)起對象匙姜。
MediaType contentType = this.contentType;
if (contentType != null) {
if (body != null) {
body = new ContentTypeOverridingRequestBody(body, contentType);
} else {
requestBuilder.addHeader("Content-Type", contentType.toString());
}
}
// 這里又通過OkHttp的Request類自身的構建者最終創(chuàng)建了Request對象
return requestBuilder
.url(url)
.method(method, body)
.build();
}
}
繼續(xù)看RequestBuild的build()的調(diào)用者是ServiceMethod的toRequest()方法:
Request toRequest(Object... args) throws IOException {
RequestBuilder requestBuilder = new RequestBuilder(httpMethod, baseUrl, relativeUrl, headers,
contentType, hasBody, isFormEncoded, isMultipart);
// ....省略代碼
for (int p = 0; p < argumentCount; p++) {
handlers[p].apply(requestBuilder, args[p]);
}
return requestBuilder.build();
}
先看看apply方法畅厢,它是ParameterHandler的抽象方法,里面有很多參數(shù)的創(chuàng)建的實現(xiàn):
@Override void apply(RequestBuilder builder, T value) {
if (value == null) {
throw new IllegalArgumentException("Body parameter value must not be null.");
}
RequestBody body;
try {
// 調(diào)用的這個convert這個方法就是上面fastjson工廠轉換創(chuàng)建請求發(fā)起RequestBody對象的調(diào)用處
body = converter.convert(value);
} catch (IOException e) {
throw new RuntimeException("Unable to convert " + value + " to RequestBody", e);
}
// 這里把創(chuàng)建的RequestBody對象設置給了RequestBuild構建者氮昧。這就是構建者的好處(初始化一個Request對象不容易框杜,屬性的初始化時機和位置有各種情況)
builder.setBody(body);
}
ServiceMethod的toRequest()方法調(diào)用者是OkHttpCall的createRawCall()
private okhttp3.Call createRawCall() throws IOException {
Request request = serviceMethod.toRequest(args);
okhttp3.Call call = serviceMethod.callFactory.newCall(request);
if (call == null) {
throw new NullPointerException("Call.Factory returned null.");
}
return call;
}
上面的代碼意思是,通過一些參數(shù)創(chuàng)建了一個請求發(fā)起對象袖肥,然后再通過一個工廠對象創(chuàng)建了一個用于發(fā)起請求的okhttp3的call對象咪辱,再來看看createRawCall()方法的調(diào)用,它有三個地方調(diào)用了:
// 一個同步的請求方法
public synchronized Request request() {}
// 異步的請求方法椎组,但是沒有請求回調(diào)
public Response<T> execute() throws IOException {}
// 異步的請求方法油狂,有請求回調(diào)接口對象處理
public void enqueue(final Callback<T> callback) {}
很明顯我們在發(fā)起一個網(wǎng)絡業(yè)務請求的時候,使用的就是enqueue(callback)方法寸癌,大概來看看具體的實現(xiàn):
@Override public void enqueue(final Callback<T> callback) {
// 這里請求回調(diào)如果是null专筷,直接就報空指針異常,這點在開發(fā)的時候需要做好非空判斷處理
if (callback == null) throw new NullPointerException("callback == null");
okhttp3.Call call;
Throwable failure;
synchronized (this) {
if (executed) throw new IllegalStateException("Already executed.");
executed = true;
call = rawCall;
failure = creationFailure; // 創(chuàng)建的時候的有可能有錯
if (call == null && failure == null) {
try {
// 初次構建使用的時候蒸苇,會去創(chuàng)建一個call
call = rawCall = createRawCall();
} catch (Throwable t) {
failure = creationFailure = t;
}
}
}
if (failure != null) {
// 出錯則回調(diào)請求失敗
callback.onFailure(this, failure);
return;
}
if (canceled) {
// 請求如有取消仁堪,則取消
call.cancel();
}
// 此處才是,真正發(fā)起請求的地方填渠,把請求交由給底層OkHttp來做弦聂。
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;
}
// 告知回調(diào)請求成功
callSuccess(response);
}
// 請求失敗
@Override public void onFailure(okhttp3.Call call, IOException e) {
try {
callback.onFailure(OkHttpCall.this, e);
} catch (Throwable t) {
t.printStackTrace();
}
}
private void callFailure(Throwable e) {
try {
callback.onFailure(OkHttpCall.this, e);
} catch (Throwable t) {
t.printStackTrace();
}
}
private void callSuccess(Response<T> response) {
try {
// 回調(diào)給業(yè)務請求調(diào)用處氛什,告知請求成功
callback.onResponse(OkHttpCall.this, response);
} catch (Throwable t) {
t.printStackTrace();
}
}
});
}
這樣倒過來分析莺葫,不知有沒有更清晰點,梳理下:
Retrofit構建的時候枪眉,為其設置了FastJson的工廠對象捺檬。
上面可知call.enqueue(callback),call就是OkHttpCall對象贸铜。
enqueue創(chuàng)建的時候會先調(diào)createRawCall
createRawCall會先調(diào)用serviceMethod的toRequest方法
在toRequest方法中堡纬,創(chuàng)建RequestBuild對象,并且把設置的業(yè)務請求的api里的參數(shù)對象請求體Body使用FastJson工廠創(chuàng)建的FastJsonRequestConverter來convert出一個RequestBody設置給RequestBuild對象蒿秦,并最終通過構建者模式創(chuàng)建Request對象烤镐。
再通過callFactory工廠創(chuàng)建一個用于請求的call,最終交由okhttp的enqueue方法來發(fā)起真正的網(wǎng)絡請求棍鳖。
總結
今天的篇幅也比較長炮叶,主要說明了抽象工廠設計模式的使用碗旅,具體舉了個在開發(fā)中比較實用的多平臺登錄的用戶系統(tǒng)模塊的問題,當然這只是個例子實際項目中需要完善的還很多镜悉。通用的例子還有很多比如:多種支付方式的切換祟辟、多種地圖SDK的切換、多種網(wǎng)絡框架的切換侣肄、多種持久化數(shù)據(jù)存儲方式的切換旧困、多種數(shù)據(jù)處理方式的切換、多種圖片加載器的切換等等稼锅。
后面主要介紹了Retrofit中抽象工廠的應用叮喳,以及簡單分析了,Retrofit是如何構建請求和發(fā)起請求的缰贝。
我是JerryloveEmily,感謝您的閱讀畔濒,
喜歡就點個贊唄剩晴,“?喜歡”,
鼓勵又不花錢侵状,您在看赞弥,我就繼續(xù)寫~
非簡書用戶,可以點右上角的三個“...”趣兄,然后"在Safari中打開”绽左,就可以點贊咯~