细细品读Retrofit的设计之美二

作者: JerryloveEmily | 来源:发表于2017-09-27 18:10 被阅读572次

本篇文章已授权为微信公众号 code小生
发布转载请注明出处:http://www.jianshu.com/p/dab7f5720aa5

1.细细品读Retrofit的设计之美一
2. 细细品读Retrofit的设计之美二


引言

在上一篇 品读Retrofit设计之美后,我们了解了Builder构建者模式和(动态)代理模式在Retrofit中的做用,以及它们的使用套路。今天继续品读Retrofit框架值得我们好好思考的设计:抽象工厂模式

抽象工厂模式

在看Retrofit的抽象工厂模式的应用前,先来了解下,抽象工厂模式的套路,不扯虚的直接举一个实用的例子:

我们都知道作为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();
}

主要就是获取不同模块的产品抽象接口对象,便于客户端使用工厂的模块对象的时候多态性。

  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中打开”,就可以点赞咯~

相关文章

  • 细细品读Retrofit的设计之美二

    本篇文章已授权为微信公众号 code小生发布转载请注明出处:http://www.jianshu.com/p/da...

  • 细细品读Retrofit的设计之美一

    本篇文章已授权为微信公众号 code小生发布转载请注明出处:http://www.jianshu.com/p/2f...

  • 简洁美

    品读智慧之美, 欣享简洁之美!

  • 细细品读

    节前最后一个周末被约携娃看展听讲解《金石书香沁后人——周叔弢捐献文物与古籍特展》。这个展我11月份篆刻收尾前凑巧看...

  • 设计模式

    Java之美[从菜鸟到高手演变]之设计模式 Java之美[从菜鸟到高手演变]之设计模式二 Java之美[从菜鸟到高...

  • 值得细细品读!

    别墅豪宅、高档家具、奢侈物件、奢华旅游,稀有器皿、俊男靓女,这些东西像磁铁一样吸引着人们。没有人会想到,物质的攀比...

  • 细细品读(摘录)

    朱光潜:文学作品,一目十行是荒谬的。金宋时期的大文学家元遗山在论文诗里说:“文须字字作,亦要字字读,咀嚼有余味,百...

  • iOS设计模式之美-适配器模式

    iOS设计模式之美-工厂模式iOS设计模式之美-抽象工厂模式iOS设计模式之美-生成器模式iOS设计模式之美-适配...

  • iOS设计模式之美-抽象工厂模式

    iOS设计模式之美-工厂模式iOS设计模式之美-抽象工厂模式iOS设计模式之美-生成器模式iOS设计模式之美-适配...

  • iOS设计模式之美-工厂模式

    iOS设计模式之美-工厂模式iOS设计模式之美-抽象工厂模式iOS设计模式之美-生成器模式iOS设计模式之美-适配...

网友评论

本文标题:细细品读Retrofit的设计之美二

本文链接:https://www.haomeiwen.com/subject/kupfextx.html