Retrofit设计模式

1,383 阅读6分钟

从Retrofit的设计模式,来进一步的学习Retrofit源码,个人觉得是认识Retrofit源码最快,也是比较容易理解的一种方式。在阅读本文之前要对Retrofit源码有过阅读,否则有些部分估计会看不懂,推荐一下我之前写的Retrofit流程解析

外观模式

外观模式的定义: 为子系统中的一组接口提供一个一致的界面,外观模式定义一个高层接口,这个接口使得这一子系统更容易使用。

外观模式将单独的模块中一个或者多个类的复杂实现隐藏起来,通过实现一个更加合理的接口的外观类,来统一处理模块中的细节。通过这种方式方便了模块的使用,并且极大的降低了模块之间的耦合。

Retrofit中的外观类就是Retrofit,对于API的调用者来说,只需要配置Retrofit,然后等待请求的回调就可以了。至于Retrofit内部的组件,包括调用适配器、互数据转换器、线程的调度等,我们完全不需要在意,达到了模块之间解耦的目的。

外观模式中使用到了迪米特法则,也叫最小只是原则: 一个对象应该对其他对象保持最小的了解。 因为类与类之间的关系越密切,耦合度越大,当一个类发生改变时,对另一个类的影响也越大,所以这也是我们提倡的软件编程的总的原则:低耦合,高内聚。

建造者模式

Retrofit对象的创建使用到了建造者模式:

val retrofit = Retrofit.Builder()
        .client(client)
        .baseUrl(HttpService.HttpUrl.url)
        .addCallAdapterFactory(KotlinCallAdapterFactory())
        .addConverterFactory(GsonConverterFactory.create())
        .addConverterFactory(SimpleXmlConverterFactory.create())
        .addConverterFactory(JaxbConverterFactory.create())
        .build()

具有良好的封装性,隐藏了内部的实现细节,用户可以按需调用并配置,灵活性较高。

动态代理

代理模式就是在委托类和客户之间添加一层代理类,通过代理类来代理委托类的实现。而动态代理在运行期通过反射生成对应的代理类。通常使用动态代理我们可以在代理操作执行之前和之后添加一些额外的操作,比如打个Log。

Retrofit中调用create方法时,会使用到动态代理。有一点要知道,动态代理中使用InvocationHandler作为中介类,并且InvocationHandler持有委托类的对象,在通过中介类来调用委托类操作的同时,我们们就可以添加一些额外的操作了。但是Retrofit比较特殊,因为在InvocationHandler中并不持有委托类对象,只是单纯的拿到我们调用的method,通过解析mehtod的注解、参数注解和返回值来动态的封装一个OkhttpCall请求。

  public <T> T create(final Class<T> service) {
    validateServiceInterface(service);
    return (T)
        Proxy.newProxyInstance(
            service.getClassLoader(),
            new Class<?>[] {service},
            new InvocationHandler() {
              private final Platform platform = Platform.get();
              private final Object[] emptyArgs = new Object[0];

              @Override
              public @Nullable 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);
                }
                args = args != null ? args : emptyArgs;
                return platform.isDefaultMethod(method)
                    ? platform.invokeDefaultMethod(method, service, proxy, args)
                    : loadServiceMethod(method).invoke(args);
              }
            });
  }

在上述的loadServiceMethod中将method当做参数最终传给了ServiceMethod解析并缓存到Map集合中。

抽象工厂模式

提供一个创建一系列相关或相互依赖对象的接口,而无需指定它们具体的类。抽象工厂模式中需要一个抽象工厂,多个具体的工厂类,一个抽象产品,多个具体的产品。每一个具体的工厂用于生产同一个产品族的产品。在Retrofit中创建ConverterCallAdapter采用了这种设计模式。接下来以Converter来解析Retrofit的抽象工厂设计:

public interface Converter<F, T> {
  @Nullable
  T convert(F value) throws IOException;

  abstract class Factory {
    public @Nullable Converter<ResponseBody, ?> responseBodyConverter(
        Type type, Annotation[] annotations, Retrofit retrofit) {
      return null;
    }
    
    public @Nullable Converter<?, RequestBody> requestBodyConverter(
        Type type,
        Annotation[] parameterAnnotations,
        Annotation[] methodAnnotations,
        Retrofit retrofit) {
      return null;
    }

    public @Nullable Converter<?, String> stringConverter(
        Type type, Annotation[] annotations, Retrofit retrofit) {
      return null;
    }
  }
}

上述代码中Factory就是抽象工厂类,Retrofit中提供了多个Factory的实现类,如GsonConverterFactory、JaxbConverterFactory等。这些实现类就是Factory的具体子工厂。抽象工厂中定义了产品族的概念,比如Factory抽象类中定义了responseBodyConverter、requestBodyConverter、stringConverter方法,具体的实现类需要实现这些方法并返回具体的Converter对象。而Converter就是对应的抽象产品,抽象工厂Factory上述的三个方法对应着不同的产品Converter

  • Converter<ResponseBody, ?> 将请求返回的ResponseBody转化为对应的泛型T指定的具体类型。
  • Converter<?, RequestBody> 将T转化为对应的RequestBody对象。
  • Converter<?, String> 将T转化为对应的String对象。 每一个抽象工厂的实现类,对应着多个产品,也就是我们所说的产品族。Retrofit中通过抽象工厂模式,将数据的转化解耦,方便我们定制数据类型的转换器。

适配器模式

将一个类的接口转换成客户希望的另外一个接口。适配器模式使得原本由于接口不兼容而不能一起工作的那些类可以一起工作。

Retrofit中通过Call接口的实现类OkHttpCall来实现网络请求,而OkHttpCall对象经过CallAdapter类的adapt方法转换成对应的实现类,从而实现请求处理、线程调度等。看过上面抽象工厂模式,应该知道Retrofit中通过实现CallAdapter.Factory抽象类来创建不同的工厂实现类来生产不同的CallAdapter抽象产品类。比如以下两种:

RxJavaCallAdapterFactory

兼容Rxjava的工厂类,通过getCallAdapter方法可以获取对应的CallAdapter对象,这里以其中的ResponseCallAdapter产品类来解析:

  static final class ResponseCallAdapter implements CallAdapter<Observable<?>> {
    ......
    @Override public <R> Observable<Response<R>> adapt(Call<R> call) {
      Observable<Response<R>> observable = Observable.create(new CallOnSubscribe<>(call));
      if (scheduler != null) {
        return observable.subscribeOn(scheduler);
      }
      return observable;
    }
  }

按照上面说的OkHttpCall对象会经过ResponseCallAdapteradapt方法,转化为Rxjava中的Observable<Response<R>>。这里的call就是我们传递进来的OkHttpCall,所以ResponseCallAdapter作为一个适配器,承担着将call对象转化为Observable的作用。

DefaultCallAdapterFactory

Retrofit中默认的适配器,返回的适配器对象为:

    return new CallAdapter<Object, Call<?>>() {
      @Override
      public Type responseType() {
        return responseType;
      }

      @Override
      public Call<Object> adapt(Call<Object> call) {
        return executor == null ? call : new ExecutorCallbackCall<>(executor, call);
      }
    };

可以看到call对象会被转化为ExecutorCallbackCall对象,ExecutorCallbackCall对象中,请求响应通过handlerpost到主线程处理。

CompletableFutureCallAdapterFactory

Retrofit中当Build.VERSION.SDK_INT >= 24时可以使用这种工厂类,如果返回类型是Response<T>,则创建一个ResponseCallAdapter对象:

private static final class ResponseCallAdapter<R>
      implements CallAdapter<R, CompletableFuture<Response<R>>> {
     ......
    @Override
    public CompletableFuture<Response<R>> adapt(final Call<R> call) {
      CompletableFuture<Response<R>> future = new CallCancelCompletableFuture<>(call);
      call.enqueue(new ResponseCallback(future));
      return future;
    }

这里将call对象转化为CompletableFuture对象。

Retrofit中通过适配器模式将call对象转化为Rxjava中的Observable,默认的ExecutorCallbackCall和CompletableFutureCallAdapterFactory中的CompletableFuture对象等。我们可以添加额外的工厂类,然后将call适配为我们需要的对象就可。

说到这里有一个Retrofit中的我觉得刚开始有些糊涂的地方,如我们在创建Retrofit对象时,传递了多个CallAdapter.Factory的具体子工厂,那么Retrofit是如何选择正确的工厂类呢,如何保证返回的类型一定是我们调用请求方法时规定的类型呢,其实Retrofit通过动态代理来生成一个代理类调用接口中的请求方法时,最终会调用到CallAdapter具体实现类的adapt方法。Retrofit在对应的CallAdapter.Factory工厂类中,首先就会判断返回的类型是否是工厂类希望的类型,比如RxJavaCallAdapterFactory中需要返回的类型是Observable,DefaultCallAdapterFactory中需要返回的类型是Call,CompletableFutureCallAdapterFactory中需要返回的类型是`CompletableFuture。如类型对不上的话,会返回一个null值,继续遍历下一个工厂类。最后找不到的话,会直接抛出异常。

//Rxjava
    if (rawType != Observable.class && !isSingle && !isCompletable) {
      return null;
    }
//默认
    if (getRawType(returnType) != Call.class) {
      return null;
    }
//CompletableFutureCallAdapterFactory
    if (getRawType(returnType) != CompletableFuture.class) {
      return null;
    }

本来讲个适配器为什么说到了这里呢,其实就是想说明适配器的一些缺点:比我这次我们需要的是Observable类型,但是在不同的适配器中返回的类型各不相同,Retrofit是如何做到将Call对象转化为确定的返回值的,对于没接触过适配器模式的人来说,理解会慢一些,所以 在项目中过多的使用适配器模式的话,看起代码估计就是很酸爽了。当然适配器模式的优点也很明显,它的灵活性更高,提高了类的复用性,特别是在Retrofit中,通过适配器来转化Call为指定的类型,从而实现类似于Rxjava这样的线程调度等功能。最后还是要根据具体的使用来判断是否使用适配器模式,或者说直接重构代码算了。

装饰模式

动态地给一个对象添加一些额外的职责。就增加功能来说,装饰模式相比生成子类更为灵活。

Retrofit中如果不设置CallAdapter.Factory且返回值类型为Call的话默认会执行到DefaultCallAdapterFactory工厂类中。再返回的CallAdapter中会将OkHttpCall对象转化为ExecutorCallbackCall对象。这里通过装饰模式,动态的给OkHttpCall添加了线程调度的功能,将请求的响应调度到主线程。

static final class ExecutorCallbackCall<T> implements Call<T> {
    final Call<T> delegate;
    ExecutorCallbackCall(Executor callbackExecutor, Call<T> delegate) {
      this.callbackExecutor = callbackExecutor;
      this.delegate = delegate;
    }

    @Override
    public void enqueue(final Callback<T> callback) {
    }
}    

具体对应装饰模式中的类型:

  • Call 接口类
  • OkHttpCall 接口实现类
  • ExecutorCallbackCall 既充当抽象装饰类,又充当具体装饰类

Retrofit中通过装饰模式为Call类增加了线程调度的功能,符合开闭原则。至于缺点就是增加功能过多的话,多层装饰之间的嵌套就比较复杂,我才这也是为什么Retrofit采用适配器模式来转化Call为指定的类型原因。

Retrofit中涉及到的设计模式还是有不少的,像简单工厂模式、工厂模式、策略模式等。本文主要是对Retrofit的源码阅读做巩固,因此有些点到为止了。