• Retrofit源码设计模式解析(下)


    本文将接着《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网络请求,敬请关注……

  • 相关阅读:
    jar包依赖整理(一)
    centos 下 tomcat 内存不足引起的错误
    KendoUI 基础:Grid 绑定template展示
    C#读取XML文件的五个步骤
    C#winform向Txt文件传值,不重复录入且不清空
    JS页面赋值
    Python3---对象编程思想
    Python3---标准库---numpy
    Python3---标准库json
    Python3---标准库sys
  • 原文地址:https://www.cnblogs.com/younghao/p/6098329.html
Copyright © 2020-2023  润新知