本文将接着《Retrofit源码设计模式解析(上)》,继续分享以下设计模式在Retrofit中的应用:

  1. 适配器模式
  2. 策略模式
  3. 观察者模式
  4. 单例模式
  5. 原型模式
  6. 享元模式

一、适配器模式

在上篇说明CallAdapter.Factory使用工厂模式时,提到CallAdapter本身采用了适配器模式。适配器模式将一个接口转换成客户端希望的另一个接口,使接口本不兼容的类可以一起工作。

Call接口是Retrofit内置的发送请求给服务器并且返回响应体的调用接口,包括同步、异步请求,查询、取消、复制等功能。

public interface Call<T> extends Cloneable {
// 同步执行请求
Response<T> execute() throws IOException;
// 异步执行请求
void enqueue(Callback<T> callback);
// 省略代码 // 取消请求
void cancel();
// 复制请求
Call<T> clone();
}

而客户端可能希望更适合业务逻辑的接口回调,比如响应式的接口回调。那么,就需要对Call进行转换,CallAdapter就上场了。CallAdapter包含两个方法:

public interface CallAdapter<T> {
// 返回请求后,转换的参数Type类型
Type responseType();
// 接口适配
<R> T adapt(Call<R> call);
}

如果客户端没有配置CallAdapter,Retrofit会采用默认的实现DefaultCallAdapterFactory直接返回Call对象,而如果配置了RxJava的RxJavaCallAdapterFactory实现,就会将Call<R>转换为Observable<R>,供客户端调用。

static final class SimpleCallAdapter implements CallAdapter<Observable<?>> {

    // 省略代码
@Override
public <R> Observable<R> adapt(Call<R> call) {
Observable<R> observable = Observable.create(new CallOnSubscribe<>(call))
.lift(OperatorMapResponseToBodyOrError.<R>instance());
if (scheduler != null) {
return observable.subscribeOn(scheduler);
}
return observable;
}
}

总结下,适配器模式包含四种角色:

  • Target:目标抽象类

  • Adapter:适配器类
  • Adaptee:适配者类
  • Client:客户端类

CallAdapter对应Target,其adapt方法返回客户端类Client需要的对象;RxJavaCallAdapterFactory的get方法返回SimpleCallAdapter对象(或ResultCallAdapter对象)实现了CallAdapter<Observable<?>>,对应Adapter;Call<R>对应Adaptee适配者类,包含需要被适配的方法。

另外,适配器模式有对象适配器和类适配器两种实现。类适配器中的Adapter需要继承自Adaptee,对象适配则是采用复合的方式,Adapter持有Adaptee的引用。类适配器模式会使Adaptee的方法暴露给Adapter,根据“复合优先于继承”的思想,推荐使用对象适配器模式。

值得说明的是,这里SimpleCallAdapter并没有通过域的方式持有Call<R>,而是直接在CallAdapter的get方法中将Call<R>以入参形式传入。虽然并不是教科书式的对象适配器模式,但使用却更加灵活、方便。

二、策略模式

完成一项任务,往往可以有多种不同的方式,每一种方式称为一个策略,我们可以根据环境或者条件的不同选择不同的策略来完成该项任务。针对这种情况,一种常规的做法是将多个策略写在一个类中,通过if…else或者switch等条件判断语句来选择具体的算法。这种方式实现简单、快捷,但维护成本很高,当添加新的策略时,需要修改源代码,这违背了开闭原则和单一原则。仍以CallAdapter为例,不同的CallAdapter代表着不同的策略,当我们调用这些不同的适配器的方法时,就能得到不同的结果,这就是策略模式。策略模式包含三种角色:

  • Context上下文环境——区别于Android的Context,这里代表操作策略的上下文;
  • Stragety抽象策略——即不同策略需要实现的方法;
  • ConcreteStragety策略实现——实现Stragety抽象策略。

在Retrofit中,配置Retrofit.Builder时addCallAdapterFactory,配置的类就对应Context;不同的CallAdapter都需要提供adapt方法,CallAdapter<T>就对应Stragety抽象策略。RxJavaCallAdapterFactory的get方法返回SimpleCallAdapter对象(或ResultCallAdapter对象)就对应具体的策略实现。

这里可能会跟上篇中的工厂模式搞混,在说明工厂模式时,主要是强调的是:

public abstract CallAdapter<?> get(Type returnType, Annotation[] annotations, Retrofit retrofit);

通过get方法返回不同的CallAdapter对象;策略模式强调的是这些不同CallAdapter对象的adapt方法的具体实现。

<R> T adapt(Call<R> call);

总结下:工厂模式强调的是生产不同的对象,策略模式强调的是这些不同对象的策略方法的具体实现,是在创建对象之后。

三、观察者模式

建立一种对象与对象之间的依赖关系,一个对象发生改变时将自动通知其他对象,其他对象将相应做出反应。在此,发生改变的对象称为观察目标,而被通知的对象称为观察者,一个观察目标可以对应多个观察者,而且这些观察者之间没有相互联系,可以根据需要增加和删除观察者,使得系统更易于扩展,这就是观察者模式的模式动机。

举个栗子:在Android编程中,常见的一种情况是界面上某个控件的状态对其它控件有约束关系,比如,需要根据某个EditText的输入值决定某个按钮是否可以点击,就需要此EditText是可观测的对象,而按钮是EditText的观测者,当EditText状态发生改变时,按钮进行相应的操作。

观察者模式包含四种角色:

  • Subject抽象主题——也就是被观察对象,Observable是JDK中内置的类(java.util.Observable),当需要定义被观察对象时,继承自Observable即可;
  • ConcreteSubject具体主题——具体被观察者,可以继承Observable实现,需要通知观察者时,调用notifyObservers;
  • Observer抽象观察者——Observer也是JDK内置的,定义了update方法;
  • ConcreteObserver具体观察者——实现Observer接口定义的update方法,以便在状态发生变化时更新自己。
public interface Observer {
void update(Observable observable, Object data);
}
public class Observable {

    List<Observer> observers = new ArrayList<Observer>();

    // 省略代码
public void notifyObservers(Object data) {
int size = 0;
Observer[] arrays = null;
synchronized (this) {
if (hasChanged()) {
clearChanged();
size = observers.size();
arrays = new Observer[size];
observers.toArray(arrays);
}
}
if (arrays != null) {
for (Observer observer : arrays) {
observer.update(this, data);
}
}
}
}

所有与网络请求相关的库一定会支持请求的异步发送,通过在库内部维护一个队列,将请求添加到该队列,同时注册一个回调接口,以便执行引擎完成该请求后,将请求结果进行回调。Retrofit也不例外,Retrofit的网络请求执行引擎是OkHttp,请求类是OkHttpCall,其实现了Call接口,enqueue方法如下,入参为Callback对象。

void enqueue(Callback<T> callback);

在OkHttpCall的enqueue实现方法中,通过在okhttp3.Callback()的回调方法中调用上述入参Callback对象的方法,实现通知观察者。

@Override
public void enqueue(final Callback<T> callback) {
// 省略代码
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 callSuccess(Response<T> response) {
try {
callback.onResponse(OkHttpCall.this, response);
} catch (Throwable t) {
t.printStackTrace();
}
}

总结下:Call接口对应Subject,定义被观察者的特性,包含enqueue等;OkHttpCall对应ConcreteSubject具体被观察者,Callback对应Observer抽象观察者,Callback的实现类对应ConcreteObserver具体观察者。

四、单例模式

单例模式可能是所有设计模式教程的第一个讲到的模式,也是应用最广泛的模式之一。Retrofit中也使用了大量的单例模式,比如BuiltInConverters的responseBodyConverter、requestBodyConverter等,并且使用了饿汉式的单例模式。由于这种单例模式应用最广,也是大家都清楚的,本节将扩展下单例模式的其它实现方式:

懒汉式单例模式:

public class Singleton {

    private static Singleton instance;

    private Singleton() {

    }

    public static synchronized Singleton getInstance() {
if (instance == null) {
instance = new Singleton();
}
return instance;
}
}

懒汉单例模式的优点是单例只要有在使用是才被实例化,缺点是美的调用getInstance都进行同步,造成不必要的同步开销。

DCL(Double Check Lock):

public class Singleton {

    private static Singleton instance;

    private Singleton() {

    }

    public static Singleton getInstance() {
if (instance == null) {
synchronized (Singleton.class) {
if (instance == null) {
instance = new Singleton();
}
}
}
return instance;
}
}

DCL是对懒汉单例模式的升级,getInstance方法对instance进行了两次判空,第一层判断是为了避免不必要的同步,第二层判断是为了在null时创建实例,这里涉及到对象实例化过程的原子问题。在Java中,创建对象并非原子操作,而是包含分配内存、初始化成员字段、引用指向等一连串操作,而多线程环境下,由于指令重排序的存在,初始化指令和引用指令可能是颠倒,那么可能当线程执行第一个判断不为null返回的对象,却是未经初始化的(别的对象创建Singleton时,初始化指令和引用指令颠倒了)。

静态内部类:

public class Singleton {

    private Singleton() {

    }

    public static Singleton getInstance() {
return SingletonHolder.instance;
} private static class SingletonHolder {
private static final Singleton instance = new Singleton();
}
}

上述DCL也是可能失效的,具体可参考《有关“双重检查锁定失效”的说明》。采用静态内部类,加载Singleton类时并不会初始化instance,同时也能保证线程安全,单例对象的唯一性。

枚举单例:

public enum  Singleton {

    INSTANCE;
}

枚举实例的创建默认是线程安全的,并且在任何情况下都只有一个实例。上述单例模式存在反序列化会重新创建对象的情况,而枚举不存在这个问题。但Android编程中,因为性能问题,不推荐使用枚举,所以,这种比较怪异的方式并不推荐。

使用容器实现单例模式:

public class Singleton {

    private static Map<String, Object> objectMap = new HashMap<>();

    public static void addObject(String key, Object instance) {
if (!objectMap.containsKey(key)) {
objectMap.put(key, instance);
}
} public static Object getObject(String key) {
return objectMap.get(key);
}
}

严格的讲,这并不是标准的单例模式,但确实实现了单例的效果。

单例的核心原理是将构造函数私有化,通过静态方法获取唯一实例。而怎么获取唯一实例?在Java中可能存在线程安全、反序列化等问题,因此衍生出上述这几个版本。在实际使用时需要根据并发环境、JDK版本以及资源消耗等因素综合考虑。

五、原型模式

原型模式是一种创建型模式,主要用于对象复制。使用原型模式创建对象比直接new一个对象在性能上要好的多,因为Object类的clone方法是一个本地方法,它直接操作内存中的二进制流。使用原型模式的另一个好处是简化对象的创建,使得创建对象就像在编辑文档时的复制粘贴。基于以上优点,在需要重复地创建相似对象时可以考虑使用原型模式。比如需要在一个循环体内创建对象,假如对象创建过程比较复杂或者循环次数很多的话,使用原型模式不但可以简化创建过程,而且可以使系统的整体性能提高很多。

原型模式有三种角色:

  • Client客户端;
  • Prototype原型——一般表现为抽象类或者接口,比如JDK中的Cloneable接口;
  • ConcretePrototype具体原型类——实现了Prototype原型。

OkHttpCall实现了Call接口,Call接口继承自Cloneable,OkHttpCall的clone方法实现如下:

@Override
public OkHttpCall<T> clone() {
return new OkHttpCall<>(serviceMethod, args);
}

clone的实现就是重新new了一个一样的对象,用于其他地方重用相同的Call,在ExecutorCallbackCall中有用到:

static final class ExecutorCallbackCall<T> implements Call<T> {
// 省略代码
@SuppressWarnings("CloneDoesntCallSuperClone") // Performing deep clone.
@Override
public Call<T> clone() {
return new ExecutorCallbackCall<>(callbackExecutor, delegate.clone());
}
}

使用原型模式复制对象需要主要深拷贝与浅拷贝的问题。Object类的clone方法只会拷贝对象中的基本的数据类型,对于数组、容器对象、引用对象等都不会拷贝,这就是浅拷贝。如果要实现深拷贝,必须将原型模式中的数组、容器对象、引用对象等另行拷贝。

六、享元模式

享元模式是对象池的一种实现,运用共享技术有效地支持大量细粒度对象的复用。系统只使用少量的对象,而这些对象都很相似,状态变化很小,可以实现对象的多次复用。由于享元模式要求能够共享的对象必须是细粒度对象,因此它又称为轻量级模式(Flyweight),它是一种对象结构型模式。

享元模式包含三种角色:

  • Flyweight享元基类或接口;
  • ConcreteFlyweight具体的享元对象;
  • FlyweightFactory享元工厂——负责管理享元对象池和创建享元对象。

Retrofit中create方法创建ServiceMethod是通过loadServiceMethod方法实现。loadServiceMethod方法就实现了享元模式。

private final Map<Method, ServiceMethod> serviceMethodCache = new LinkedHashMap<>();

ServiceMethod loadServiceMethod(Method method) {
ServiceMethod result;
synchronized (serviceMethodCache) {
result = serviceMethodCache.get(method);
if (result == null) {
result = new ServiceMethod.Builder(this, method).build();
serviceMethodCache.put(method, result);
}
}
return result;
}

上篇讲到代理模式的时候,提到了这个方法的缓存使用了LinkedHashMap,系统中的Method接口数相对于请求次数是有数量级差距的,把这些接口的信息缓存起来是非常有必要的一个优化手段,这样的实现方式就是享元模式。

在享元模式中共享的是享元对象的内部状态,外部状态需要通过环境来设置。在实际使用中,能够共享的内部状态是有限的,因此享元对象一般都设计为较小的对象,它所包含的内部状态较少,这种对象也称为细粒度对象。享元模式的目的就是使用共享技术来实现大量细粒度对象的复用。在经典享元模式中,它的键是享元对象的内部状态,它的值就是享元对象本身。上述serviceMethodCache的key是method,value是ServiceMethod,method就是ServiceMethod的内部状态。

总结:Retrofit不愧是大师之作,设计模式的经典教程。其源码量并不大,但系统的可扩展性、可维护性极强,是客户端架构设计的典范,非常值得学习,五星推荐!

(后续笔者会分享,在Retrofit基础上封装更符合业务需求的Android网络请求,敬请关注……)

Retrofit源码设计模式解析(下)的更多相关文章

  1. Retrofit源码设计模式解析(上)

    Retrofit通过注解的方法标记HTTP请求参数,支持常用HTTP方法,统一返回值解析,支持异步/同步的请求方式,将HTTP请求对象化,参数化.真正执行网络访问的是Okhttp,Okhttp支持HT ...

  2. Retrofit源码解析(上)

    简介Retrofit是Square公司开发的一款针对Android网络请求的框架,官网地址http://square.github.io/retrofit/ ,在官网上有这样的一句话介绍retrofi ...

  3. [Spark内核] 第31课:Spark资源调度分配内幕天机彻底解密:Driver在Cluster模式下的启动、两种不同的资源调度方式源码彻底解析、资源调度内幕总结

    本課主題 Master 资源调度的源码鉴赏 [引言部份:你希望读者看完这篇博客后有那些启发.学到什么样的知识点] 更新中...... 资源调度管理 任务调度与资源是通过 DAGScheduler.Ta ...

  4. spring源码深度解析— IOC 之 默认标签解析(下)

    在spring源码深度解析— IOC 之 默认标签解析(上)中我们已经完成了从xml配置文件到BeanDefinition的转换,转换后的实例是GenericBeanDefinition的实例.本文主 ...

  5. [置顶] 【Android实战】----从Retrofit源码分析到Java网络编程以及HTTP权威指南想到的

    一.简介 接上一篇[Android实战]----基于Retrofit实现多图片/文件.图文上传中曾说非常想搞明白为什么Retrofit那么屌.最近也看了一些其源码分析的文章以及亲自查看了源码,发现其对 ...

  6. Retrofit源码研究

    2016-05-06 15:35:27 最近抽空研究了一下Retrofit源码,包括API使用.源码结构.使用到的设计模式.SDK的架构设计.作者设计/实现思路等,会形成一系列文章. 以前Retrof ...

  7. mybatis 3.x源码深度解析与最佳实践(最完整原创)

    mybatis 3.x源码深度解析与最佳实践 1 环境准备 1.1 mybatis介绍以及框架源码的学习目标 1.2 本系列源码解析的方式 1.3 环境搭建 1.4 从Hello World开始 2 ...

  8. Kotlin系列之序列(Sequences)源码完全解析

    Kotlin系列之序列(Sequences)源码完全解析 2018年06月05日 22:04:50 mikyou 阅读数:179 标签: Kotlin序列(sequence)源码解析Androidja ...

  9. Spring框架之beans源码完全解析

    导读:Spring可以说是Java企业开发里最重要的技术.而Spring两大核心IOC(Inversion of Control控制反转)和AOP(Aspect Oriented Programmin ...

随机推荐

  1. hdu 2014鞍山赛区 5073 Galaxy

    题意:就是给你 n 个数,代表n个星球的位置,每一个星球的重量都为 1 ! 开始的时候每一个星球都绕着质心转动,那么质心的位置就是所有的星球的位置之和 / 星球的个数 现在让你移动 k 个星球到任意位 ...

  2. ruby -- 问题解决(五)页面返回跳转

    今天在做页面跳转的时候,google了下页面跳转的方法, 跳转到上一个页面:redirect_to :back <%= link_to "返回" ,:back %> 这 ...

  3. Kafka集群部署

    一. 关于kafka Kafka是一种高吞吐量的分布式发布订阅消息系统,它可以处理消费者规模的网站中的所有动作流数据. 这种动作(网页浏览,搜索和其他用户的行动)是在现代网络上的许多社会功能的一个关键 ...

  4. Android 学习笔记之网络通信基础+WebView....

    PS:加快学习进度...下周一完成Android网络通信...然后正式进入实战... 学习内容: 1.Android中Http基础... 2.Android中的Socket基础... 3.Androi ...

  5. html5中的大纲

    html5中的大纲 前言: 在html5中我们可以使用结构元素来编排一份大纲,这样我们就可以通过这个网页的大纲来了解网页中有哪些内容,网页中以什么样的形式来组织这些内容有更清楚的认识. 1.html5 ...

  6. TinyOS和Deluge的安装模拟(一)

    介绍 TinyOS是一款嵌入式操作系统,相信做无线传感器网络开发的同志们都不陌生.同类型的系统有不少,但是TinyOS的应用较之其他系统更为广泛.TinyOS 1.x版本和2.x版本是目前主要的两个分 ...

  7. 给文本框添加模糊搜索功能(“我记录”MVC框架下实现)

    步骤: 1.在文本框中输入内容时,触发keyup事件: 2.在keyup事件的处理方法中,通过Ajax调用控制器的方法: 3.在控制器方法中,搜索满足条件的数据,这里分页获取数据,且只取第一页的数据, ...

  8. Winform开发框架之权限管理系统改进的经验总结(3)-系统登录黑白名单的实现

    在一般的权限系统里面,可能经常会看到系统的黑名单或者白名单的拦截功能.在一般权限系统里面,常见的黑名单就是禁止用户在某些IP上登录系统,白名单就是允许用户只在某些IP上登录系统.本随笔主要介绍在我的权 ...

  9. ACCESS作为网站数据库的弊端

    现在网上绝大多数网站都是ACCESS+ASP的形式,因为ACCESS结构简单容易处理,而且也能满足多数的网站程序要求. ACCESS是小型数据库,既然是小型就有他根本的局限性,以下几种情况下数据库基本 ...

  10. [moka同学收藏]Yii2.0 rules验证规则

    required : 必须值验证属性 [['字段名'],required,'requiredValue'=>'必填值','message'=>'提示信息']; #说明:CRequiredV ...