Android 网络框架--Retrofit2 架构分析

面向接口编程

面向接口编程,模块化编程的必备技能,其乃实现解耦,增强扩展性的重要手段。

面向接口编程具体指的是什么呢?

首先说一下什么是面向对象编程,大家都知道,面向对象编程是相对于面向过程编程来说的,基本上,具有对象概念的程序设计都可以称为面向对象编程。而面向接口编程仅仅是面向对象编程的一种模块化实现形式,其从组件的角度来进行代码设计,将抽象与实现分离。

接口泛指实体把自己提供给外界的一种抽象物,利用由内部操作分离出的外部沟通方法,使得实体能被修改内部而不影响外界其他实体与自己交互的方式。面向接口编程中的“接口”,在Java语言中,不仅仅是“interface”关键字这么简单,interface、abstract class以及普通的class都能成为所谓的接口。

interface约定的是务必实现的方法,强调的是规则的制定。abstract class则是在抽象的同时允许提供一些默认的行为,以达到代码复用的效果。一个实现类(相对于抽象而言)可以实现多个interface,而只能继承一个abstract class。

面向接口编程能够带来什么呢?

面向接口编程可以降低代码间的耦合,增强代码的扩展性,而正是这种特性,使得多人同时开发变成了可能。其实大部分设计模式就是面向接口编程很好的例子。

面向接口编程如何实现呢?

进行面向接口编程实操时,我们一般注意三点:先定义接口(接口或者抽象类),再定义实现类;在函数间传入传出的是接口而不是实现类;一方只认识另一方的接口,想进入另一方,就去调用另一方所披上的接口外套。

从下图中我们可以看到Retrofit用到了哪些接口类:


Retrofit接口类.png

设计模式

我们从Retrofit的调用流程来分析用到的设计模式

创建Retrofit对象

Retrofit retrofit = new Retrofit.Builder()
    .baseUrl(API_URL)
    .addConverterFactory(GsonConverterFactory.create())
    .build();

建造者模式--摘自《Java与模式》
建造者模式可以将一个产品的内部表象与产品的生成过程分割开来,从而可以使一个建造过程生成具有不同的内部表象的产品对象。
在很多情况下,建造者模式实际上是将一个对象的性质建造过程外部化到独立的建造者对象中,并通过一个导演者角色对这些外部化的性质赋值过程进行协调。

以下情况使用建造者模式

  • 需要生成的产品对象有复杂的内部结构
  • 需要生成的产品对象的属性相互依赖
  • 需要生成的产品对象有多个可选的构造参数
  • 防止生成的产品对象的参数被再次修改

创建网络请求接口的实例

GitHub github = retrofit.create(GitHub.class);

门面模式--摘自《Java与模式》
门面模式要求一个子系统的外部与其内部的通信必须通过一个统一的门面(Facade)对象进行,门面提供一个高层次的接口,使得子系统更易于使用
门面模式的门面类将客户端与子系统的内部复杂性分隔开,使得客户端只需要与门面对象打交道,而不需要与子系统内部的很多对象打交道

以下情况使用门面模式

  • 为了使得子系统更具可复用性时,可以使用Facade模式为一个复杂子系统提供一个简单接口
  • 为了得到子系统独立性和可移植性时,可以使用Facade模式将一个子系统与他的客户端以及其他子系统分离
  • 在构建一个层次化的系统时,可以使用Facade模式定义系统中每一层的入口

创建网络请求接口中方法的Call实例

代码块一
Call<List<Contributor>> call = github.contributors("square", "retrofit");
@Override public 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);
  }
  if (platform.isDefaultMethod(method)) {
    return platform.invokeDefaultMethod(method, service, proxy, args);
  }
  ServiceMethod<Object, Object> serviceMethod =
      (ServiceMethod<Object, Object>) loadServiceMethod(method);
  OkHttpCall<Object> okHttpCall = new OkHttpCall<>(serviceMethod, args);
  return serviceMethod.callAdapter.adapt(okHttpCall);
}

代理模式(本代码块为动态代理)--摘自《Java与模式》
代理主题并不改变主题的接口,因为模式的用意是不让客户端感觉到代理的存在
代理使用委派将客户端的调用委派给真实的主题对象,换言之,代理主题起到的是一个传递请求的作用
代理主题在传递请求之前和之后都可以执行特定的操作,而不是单纯传递请求

以下情况使用代理模式

  • 远程代理:系统可以将网络的细节隐藏起来,使得客户端不必考虑网络的存在
  • 虚拟代理:代理对象可以在必要的时候才将被代理的对象加载
  • 保护代理:在运行时间对用户的相关权限进行检查,然后在核实后决定是否将调用传递给被代理的对象
  • 智能引用代理:在访问一个对象时可以执行一些内务处理操作,比如计数或者日志操作
代码块二
public CallAdapter<?, ?> nextCallAdapter(@Nullable CallAdapter.Factory skipPast, Type returnType, Annotation[] annotations) {
  int start = adapterFactories.indexOf(skipPast) + 1;
  for (int i = start, count = adapterFactories.size(); i < count; i++) {
    CallAdapter<?, ?> adapter = adapterFactories.get(i).get(returnType, annotations, this);
    if (adapter != null) {
      return adapter;
    }
  }

策略模式--摘自《Java与模式》
其是对算法的包装,是把使用算法的责任和算法本身分隔开,委派给不同的对象管理。
其通常把一个系列的算法包装到一系列的策略类里面,作为一个抽象策略类的子类。即,准备一组算法,并将每个算法封装到具有共同接口的独立的类中,使得它们可以互换。
其并不决定在何时使用何种算法,应当由客户端自己决定在什么情况下使用什么具体策略角色。

以下情况使用策略模式

  • 一个系统中有许多类,而它们的区别仅在于它们的行为
  • 一个系统需要动态的在几种算法中选择一种
  • 一个系统的算法使用的数据不可以让客户端知道
  • 一个对象有很多的行为,如果不用恰当的模式,这些行为就只好使用多重的条件选择语句来实现,而此时可以考虑使用策略模式来解决。
代码块三
return new CallAdapter<Object, Call<?>>() {
  @Override public Type responseType() {
    return responseType;
  }

  @Override public Call<Object> adapt(Call<Object> call) {
    return new ExecutorCallbackCall<>(callbackExecutor, call);
  }
};

@Override public Object adapt(Call<R> call) {
  OnSubscribe<Response<R>> callFunc = isAsync
      ? new CallEnqueueOnSubscribe<>(call)
      : new CallExecuteOnSubscribe<>(call);

  OnSubscribe<?> func;
  if (isResult) {
    func = new ResultOnSubscribe<>(callFunc);
  } else if (isBody) {
    func = new BodyOnSubscribe<>(callFunc);
  } else {
    func = callFunc;
  }
  Observable<?> observable = Observable.create(func);

  if (scheduler != null) {
    observable = observable.subscribeOn(scheduler);
  }

  if (isSingle) {
    return observable.toSingle();
  }
  if (isCompletable) {
    return observable.toCompletable();
  }
  return observable;
}

适配器模式--摘自《Java与模式》
把一个类的接口变换成客户端所期待的另一种接口,从而使原本因接口不匹配而无法在一起工作的两个类能够在一起工作。

以下情况使用适配器模式

  • 系统需要使用现有的类,而此类的接口不符合系统的需要
  • 想要建立一个可以重复使用的类,用于与一些彼此之间没有太大关联的一些类(包括一些可能在将来引进的类)一起工作

发送网络请求

static final class ExecutorCallbackCall<T> implements Call<T> {
  final Executor callbackExecutor;
  final Call<T> delegate; //注意,变量类型是父类的类型

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

  @Override public void enqueue(final Callback<T> callback) {
    checkNotNull(callback, "callback == null");
 
    delegate.enqueue(new Callback<T>() {
      @Override public void onResponse(Call<T> call, final Response<T> response) {
        callbackExecutor.execute(new Runnable() {
          @Override public void run() {
            if (delegate.isCanceled()) {
              // Emulate OkHttp's behavior of throwing/delivering an IOException on cancellation.
              callback.onFailure(ExecutorCallbackCall.this, new IOException("Canceled"));
            } else {
              callback.onResponse(ExecutorCallbackCall.this, response);
            }
          }
        });
      }

      @Override public void onFailure(Call<T> call, final Throwable t) {
        callbackExecutor.execute(new Runnable() {
          @Override public void run() {
            callback.onFailure(ExecutorCallbackCall.this, t);
          }
        });
      }
    });
  }
}

装饰模式--摘自《Java与模式》
以对客户透明的方式动态地给一个对象附加上更多的责任。换言之,客户端并不会觉得对象在装饰前和装饰后有什么不同
可以在不使用创造更多子类的情况下,将对象的功能加以扩展,是继承关系的一个替代方案
纯粹的装饰模式很难找到,装饰模式用意在不改变接口的前提下,增强被装饰类的性能,即要满足透明性。在增强性能的时候,往往需要建立新的公开方法。这就导致了大多数的装饰模式的实现都是“半透明”的。这意味着客户端不去声明抽象父类类型的变量,而是声明具体装饰类类型的变量,从而可以调用具体装饰类中才有的方法。

以下情况使用装饰模式

  • 需要扩展一个类的功能,或给一个类增加附加责任
  • 需要动态地给一个对象增加功能,这些功能可以再动态地撤销
  • 需要增加由一些基本功能的排列组合而产生的非常大量的功能,从而使继承关系变得不现实

对于这一块代码,网络上也有人将其归类为代理模式,本兽下面说一下自己的观点。
装饰模式、适配器模式、代理模式都是包装(Wrapper)模式,首先说一下他们之间的区别。

  • 装饰模式&适配器模式:适配器模式把一个API转换成另一个API,而装饰模式是保持被包装的对象的API。用Java术语来讲,适配器和被适配的类实现的是不同的接口和抽象类,而装饰模式和被装饰的类实现的是相同的接口和抽象类。半透明的装饰模式实际上就是处于在适配器模式与装饰模式之间的灰色地带。
  • 装饰模式&代理模式:两种模式相同点都是保持被包装的对象的API。但是,装饰模式为所装饰的对象提供增强功能,而代理模式则为所代理对象的使用施加控制。

本兽认为,这一块代码,将其归类为装饰模式或者代理模式,都是可以的。因为设计模式本就是属于概念性的,指导性的范畴,各个设计模式其实没有那么清晰的界限,没必要非要分个你清我楚,只要都满足基本的设计原则(S.O.L.I.D)即可。

但如果非要较真(程序员的特点),到底偏向于装饰模式还是偏向于代理模式,本兽认为其更偏向于装饰模式,进一步来讲,其更偏向于简化后的装饰模式。

装饰模式简易类图如下:


装饰模式简略类图.png

简化后的装饰模式简易类图如下:


简化后的装饰模式简易类图.png

代理模式简易类图如下:


代理模式简易类图.png

大家可以看到,简化后的装饰模式的简易类图和代理模式的简易类图,非常相像。相比较之后,不同点如下:

  • 装饰类含有的变量的类型,是父类的类型,而这个变量本身一般就是左侧的被装饰类的实例
  • 代理类含有的变量的类型,直接就是左侧被代理类的类型,从而这个变量本身必然是左侧被代理类的实例

根据上述区别,再对应到代码块,我们就可以得出结论,其更偏向于简化后的装饰模式。

处理返回数据

NA

总结

Retrofit更像是一个组织者,他把几个框架高效的组合起来,在解耦的同时也满足了扩展性:其利用OkHTTP进行网络请求;与异步请求框架和类解析框架解耦,使得Retrofit可以适配多种框架,使用者可以轻松的选择或者自创适合自己项目的异步请求和解析的框架。

读者可以好好体会一下其是如何玩转各种设计模式,把面向接口编程发挥得淋漓尽致的。

Retrofit 2.0之后网络只支持OKHttp,对OKHttp强依赖。以后如果有新的网络框架出现,将无法使Retrofit。但是Retrofit提供的封装网络框架的思路依然值得借鉴。


最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 159,117评论 4 362
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 67,328评论 1 293
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 108,839评论 0 243
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 44,007评论 0 206
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 52,384评论 3 287
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 40,629评论 1 219
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 31,880评论 2 313
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 30,593评论 0 198
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 34,313评论 1 243
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 30,575评论 2 246
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 32,066评论 1 260
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 28,392评论 2 253
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 33,052评论 3 236
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 26,082评论 0 8
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 26,844评论 0 195
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 35,662评论 2 274
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 35,575评论 2 270

推荐阅读更多精彩内容

  • Android 自定义View的各种姿势1 Activity的显示之ViewRootImpl详解 Activity...
    passiontim阅读 170,569评论 25 707
  • Spring Cloud为开发人员提供了快速构建分布式系统中一些常见模式的工具(例如配置管理,服务发现,断路器,智...
    卡卡罗2017阅读 134,100评论 18 139
  • 第一天,久违的寒假班的感觉。似乎又找到一些上课的热情,虽然还是靠着之前的功力+临场发挥,但还算是凑合着。 ...
    鸣人君不见子阅读 126评论 0 0
  • 七月的城市 执着与迷惘 没有尽头地等待 看不见未来的期许 焦灼 慵懒的小猫 和连绵不断的雨 齐齐来敲窗 这个夏天 ...
    梦槿馨阅读 130评论 0 1
  • 春天来了,春天意味着什么,提到春天大家脑海中第一印象又是什么,大家可能都有自己的想法,有人觉得春天是万物复苏的季节...
    小猪de脚印阅读 192评论 0 0