細細品讀Retrofit的設計之美二

本篇文章已受權爲微信公衆號 code小生  發佈轉載請註明出處:www.jianshu.com/p/dab7f5720…java

1.細細品讀Retrofit的設計之美一 2. 細細品讀Retrofit的設計之美二json


引言

在上一篇 品讀Retrofit設計之美後,咱們瞭解了Builder構建者模式和(動態)代理模式在Retrofit中的作用,以及它們的使用套路。今天繼續品讀Retrofit框架值得咱們好好思考的設計:抽象工廠模式設計模式

抽象工廠模式

在看Retrofit的抽象工廠模式的應用前,先來了解下,抽象工廠模式的套路,不扯虛的直接舉一個實用的例子:api

咱們都知道做爲app開發者,一般的app應用都會有用戶系統,一個用戶系統每每都包含了如下模塊:1. 登陸模塊。 2. 註冊模塊。 3. 找回密碼模塊。 4. 用戶我的信息模塊。 這幾個模塊表明工廠須要生產的不一樣類型的產品,用戶系統賬號,咱們多是app自身的賬號、密碼、或者手機短信驗證碼的登陸方式,也多是第三方平臺賬號登陸:微信、QQ、新浪微博等等。對於不一樣的平臺的用戶賬號咱們能夠看作不一樣的品牌工廠,好比:app自身的用戶賬號工廠、微信的用戶賬號工廠、QQ的用戶賬號工廠、新浪微博的用戶賬號工廠。數組

這樣來設計一個用戶系統是否是更清晰點,並且不一樣的品牌的工廠便於替換,也就是替換登陸的平臺,不一樣的產品模塊類的功能職責也變的比較單一符合設計模式的單一原則。微信

案例實現

  1. 首先抽象出各個產品接口出來,每種模塊產品都有各自的功能
// ******************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);
}
複製代碼

這些產品模塊的接口規範功能抽象,對於app的用戶系統來講基本夠用了。固然上面的這些接口,也能夠統一用一個接口文件來寫,這些模塊就做爲子接口嵌套在裏面,這是爲了方便管理。網絡

  1. 而後是工廠的抽象接口,用於生產不一樣品牌的不一樣產品
// ******************IUserSystemFactory .java,抽象的工廠接口
/** * 用戶系統抽象工廠:登陸、註冊、找回密碼、用戶信息等模塊 */
public interface IUserSystemFactory {

    // 獲取登陸模塊,登陸器
    ILoginer getLoginer();

    // 獲取註冊模塊,註冊器
    IRegister getRegister();

    // 找回密碼模塊
    IFindPwder getFindPwder();

    // 用戶信息模塊
    IUserInfoer getUserInfoer();
}
複製代碼

主要就是獲取不一樣模塊的產品抽象接口對象,便於客戶端使用工廠的模塊對象的時候多態性。app

  1. 實現不一樣登陸方式的工廠和具體的用戶系統模塊

由於用戶系統大部分狀況下都須要和UI交互,因此封裝了一層基類把Context上下文統一塊兒來,減小子類的沒必要要的重複。框架

// *************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
/** * 用戶系統工廠基類 */
public abstract class BaseUserSystemFactory implements IUserSystemFactory {

    private Context mContext;

    public BaseUserSystemFactory(Context context) {
        this.mContext = context;
    }

    // 工廠對象能夠獲取上下文
    public Context getContext(){
        return mContext;
    }
}
複製代碼

好比,當咱們使用app本身的用戶賬號登陸的時候的實現異步

// ******************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
/** * 系統賬號登陸時的用戶系統工廠 */
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() {
        // 返回對應的登陸產品(app本身的賬號平臺登陸對象)
        return new SystemAccountLoginer(getContext());
    }

    @Override
    public IRegister getRegister() {
        // 返回對應的註冊產品(app本身的賬號平臺註冊對象)
        return null;
    }

    @Override
    public IFindPwder getFindPwder() {
        // 返回對應的找回密碼產品(app本身的賬號平臺找回密碼對象)
        return null;
    }

    @Override
    public IUserInfoer getUserInfoer() {
        // 返回對應的用戶信息產品(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
/** * 系統賬號登陸時的用戶系統工廠 */
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;
    }
}
複製代碼

這裏我實現了登陸產品模塊的,其它的模塊也是同樣的。對於調用者的使用也很簡單:

// 客戶端調用
// 使用本身的賬號平臺
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();
複製代碼

對於調用者來講很簡單,只要關心當前用的是什麼平臺的賬號系統,而不須要關心具體的實現方式。也把不一樣平臺的登陸、註冊、獲取用戶信息等分離開來。固然每每不一樣的平臺可能退出當前賬號的方式是同樣,這個時候,其實能夠把BaseLoginer當作代理對象,目標接口就是ILoginer,目標對象另外新建一個類實現目標接口,利用代理模式。

Retrofit抽象工廠的應用

咱們都知道網絡請求通信,當服務端返回數據後,都須要進行解析轉換爲能夠直接使用的實體對象,便於設置顯示到UI界面上,咱們在構建Retrofit對象的時候每每會給構建器注入一個解析轉換器工廠對象。

new Retrofit.Builder()
                .baseUrl(AppConst.BASE_URL)
                .client(buildHttpClient())
                .addConverterFactory(FastJsonConverterFactory.create())
                .build();
複製代碼

其中FastJsonConverterFactory.create()建立的就是一個Factory抽象工廠對象。

// 數據轉換器抽象產品類
// F是入參,T是出參(轉換後的數據類型)
public interface Converter<F, T> {
  // 產品的轉換操做
  T convert(F value) throws IOException;

  // 抽象工廠類
  abstract class Factory {
    // 工廠生產的請求響應的轉換器產品
    public Converter<ResponseBody, ?> responseBodyConverter(Type type, Annotation[] annotations,
        Retrofit retrofit) {
      return null;
    }

    // 工廠生產的請求發起的轉換器產品
    public Converter<?, RequestBody> requestBodyConverter(Type type,
        Annotation[] parameterAnnotations, Annotation[] methodAnnotations, Retrofit retrofit) {
      return null;
    }

   // 工廠生產的用於轉換字符串數據類型的轉換器產品
    public Converter<?, String> stringConverter(Type type, Annotation[] annotations,
        Retrofit retrofit) {
      return null;
    }
  }
}
複製代碼

接下來看看使用FastJson做爲轉換器的工廠實現類:

public class FastJsonConverterFactory extends Converter.Factory {
  // 建立工廠對象
  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方法,來建立工廠對象,外部調用者就不須要關係工廠對象是如何建立的。這點和我上面舉的例子是同樣的。再一個經過responseBodyConverter、requestBodyConverter方法分別建立了請求響應和請求發起這兩種產品的對象。

再來看看FastJsonRequestBodyConverter請求發起轉換產品的實現:

// 實現了轉換器這抽象產品類,入參是RequestBody,返回的結果是泛型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);
  }
}
複製代碼

實現了轉換器這抽象產品接口類,入參是RequestBody,返回的結果是泛型T(由於請求的參數是針對具體業務的做爲框架沒法肯定,因而用泛型來代替),這個FastJsonRequestBodyConverter產品的功能就是convert轉換功能,這裏使用了阿里巴巴的json解析庫fastJson來轉換,具體的實現就是經過JSON.toJSONBytes方法轉換出json的字節數組,而後交由給OkHttp的RequestBody.create來構建一個請求體,而且請求的多媒體類型是json格式的。OkHttp中的實現:

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請求的參數內容都經過Okio的BufferedSink來寫入了
        sink.write(content, offset, byteCount);
      }
    };
  }
複製代碼

你會發現RequestBody是個抽象類,writeTo是個抽象方法,那麼一定就有調用此方法的地方。也不能盲目的看源碼找,一個請求的構建最好的地方就是發起請求的時候,call.enqueue(callback),經過enqueue發起一個異步的請求,但Call是接口,也不曉得實現類。還有個辦法就是倒退的方式,將光標放置上門的writeTo方法上,按組合鍵(有使用到writeTo的地方):ctrl + alt + F7:

有使用到writeTo的地方

很明顯是最後一個ReqeustBuilder,請求構建類,跟進去是ContentTypeOverridingRequestBody,它是個代理類,目標對象是其內部的RequestBody對象這個對象咱們猜想就是上文FastJsonRequestBodyConverter的converter轉換建立的RequestBody。再來看看ContentTypeOverridingRequestBody在RequestBuild的build()構建方法中有使用:

// 很明顯由於請求對象初始化比較複雜,就經過構建者模式構建了一個OkHttp的Request對象
class RequestBuild{
  Request build() {
    // 很明顯咱們在構建Retrofit的時候有傳入FastJson的請求發起產品的生成工廠對象,所以姑且任務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建立的body目標對象本身來調用的
    // 然後把代理對象body給了Request進行構建請求發起對象。
    MediaType contentType = this.contentType;
    if (contentType != null) {
      if (body != null) {
        body = new ContentTypeOverridingRequestBody(body, contentType);
      } else {
        requestBuilder.addHeader("Content-Type", contentType.toString());
      }
    }

    // 這裏又經過OkHttp的Request類自身的構建者最終建立了Request對象
    return requestBuilder
        .url(url)
        .method(method, body)
        .build();
  }
}
複製代碼

繼續看RequestBuild的build()的調用者是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的抽象方法,裏面有不少參數的建立的實現:

image.png

@Override void apply(RequestBuilder builder, T value) {
      if (value == null) {
        throw new IllegalArgumentException("Body parameter value must not be null.");
      }
      RequestBody body;
      try {
        // 調用的這個convert這個方法就是上面fastjson工廠轉換建立請求發起RequestBody對象的調用處
        body = converter.convert(value);
      } catch (IOException e) {
        throw new RuntimeException("Unable to convert " + value + " to RequestBody", e);
      }
      // 這裏把建立的RequestBody對象設置給了RequestBuild構建者。這就是構建者的好處(初始化一個Request對象不容易,屬性的初始化時機和位置有各類狀況)
      builder.setBody(body);
    }
複製代碼

ServiceMethod的toRequest()方法調用者是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;
  }
複製代碼

上面的代碼意思是,經過一些參數建立了一個請求發起對象,而後再經過一個工廠對象建立了一個用於發起請求的okhttp3的call對象,再來看看createRawCall()方法的調用,它有三個地方調用了:

// 一個同步的請求方法
public synchronized Request request() {}

// 異步的請求方法,可是沒有請求回調
public Response<T> execute() throws IOException {}

// 異步的請求方法,有請求回調接口對象處理
public void enqueue(final Callback<T> callback) {}
複製代碼

很明顯咱們在發起一個網絡業務請求的時候,使用的就是enqueue(callback)方法,大概來看看具體的實現:

@Override public void enqueue(final Callback<T> callback) {
    // 這裏請求回調若是是null,直接就報空指針異常,這點在開發的時候須要作好非空判斷處理
    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;    // 建立的時候的有可能有錯
      if (call == null && failure == null) {
        try {
          // 初次構建使用的時候,會去建立一個call
          call = rawCall = createRawCall();
        } catch (Throwable t) {
          failure = creationFailure = t;
        }
      }
    }

    if (failure != null) {
      // 出錯則回調請求失敗
      callback.onFailure(this, failure);
      return;
    }

    if (canceled) {
     // 請求若有取消,則取消
      call.cancel();
    }

    // 此處纔是,真正發起請求的地方,把請求交由給底層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;
        }
        // 告知回調請求成功
        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 {
          // 回調給業務請求調用處,告知請求成功
          callback.onResponse(OkHttpCall.this, response);
        } catch (Throwable t) {
          t.printStackTrace();
        }
      }
    });
  }
複製代碼

這樣倒過來分析,不知有沒有更清晰點,梳理下:

  1. Retrofit構建的時候,爲其設置了FastJson的工廠對象。

  2. 上面可知call.enqueue(callback),call就是OkHttpCall對象。

  3. enqueue建立的時候會先調createRawCall

  4. createRawCall會先調用serviceMethod的toRequest方法

  5. 在toRequest方法中,建立RequestBuild對象,而且把設置的業務請求的api裏的參數對象請求體Body使用FastJson工廠建立的FastJsonRequestConverter來convert出一個RequestBody設置給RequestBuild對象,並最終經過構建者模式建立Request對象。

  6. 再經過callFactory工廠建立一個用於請求的call,最終交由okhttp的enqueue方法來發起真正的網絡請求。


總結

今天的篇幅也比較長,主要說明了抽象工廠設計模式的使用,具體舉了個在開發中比較實用的多平臺登陸的用戶系統模塊的問題,固然這只是個例子實際項目中須要完善的還不少。通用的例子還有不少好比:多種支付方式的切換、多種地圖SDK的切換、多種網絡框架的切換、多種持久化數據存儲方式的切換、多種數據處理方式的切換、多種圖片加載器的切換等等。

後面主要介紹了Retrofit中抽象工廠的應用,以及簡單分析了,Retrofit是如何構建請求和發起請求的。


我是JerryloveEmily,感謝您的閱讀,

喜歡就點個讚唄,「❤喜歡」,

鼓勵又不花錢,您在看,我就繼續寫~

非簡書用戶,能夠點右上角的三個「...」,而後"在Safari中打開」,就能夠點贊咯~

相關文章
相關標籤/搜索