Retrofit源碼設計模式解析(下)

本文將接着《Retrofit源碼設計模式解析(上)》,繼續分享如下設計模式在Retrofit中的應用:html

  1. 適配器模式
  2. 策略模式
  3. 觀察者模式
  4. 單例模式
  5. 原型模式
  6. 享元模式

1、適配器模式

在上篇說明CallAdapter.Factory使用工廠模式時,提到CallAdapter自己採用了適配器模式。適配器模式將一個接口轉換成客戶端但願的另外一個接口,使接口本不兼容的類能夠一塊兒工做。java

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>以入參形式傳入。雖然並非教科書式的對象適配器模式,但使用卻更加靈活、方便。服務器

2、策略模式

完成一項任務,每每能夠有多種不一樣的方式,每一種方式稱爲一個策略,咱們能夠根據環境或者條件的不一樣選擇不一樣的策略來完成該項任務。針對這種狀況,一種常規的作法是將多個策略寫在一個類中,經過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);

總結下:工廠模式強調的是生產不一樣的對象,策略模式強調的是這些不一樣對象的策略方法的具體實現,是在建立對象以後。

3、觀察者模式

創建一種對象與對象之間的依賴關係,一個對象發生改變時將自動通知其餘對象,其餘對象將相應作出反應。在此,發生改變的對象稱爲觀察目標,而被通知的對象稱爲觀察者,一個觀察目標能夠對應多個觀察者,並且這些觀察者之間沒有相互聯繫,能夠根據須要增長和刪除觀察者,使得系統更易於擴展,這就是觀察者模式的模式動機。

舉個栗子:在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具體觀察者。

4、單例模式

單例模式多是全部設計模式教程的第一個講到的模式,也是應用最普遍的模式之一。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版本以及資源消耗等因素綜合考慮。

5、原型模式

原型模式是一種建立型模式,主要用於對象複製。使用原型模式建立對象比直接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方法只會拷貝對象中的基本的數據類型,對於數組、容器對象、引用對象等都不會拷貝,這就是淺拷貝。若是要實現深拷貝,必須將原型模式中的數組、容器對象、引用對象等另行拷貝。

6、享元模式

享元模式是對象池的一種實現,運用共享技術有效地支持大量細粒度對象的複用。系統只使用少許的對象,而這些對象都很類似,狀態變化很小,能夠實現對象的屢次複用。因爲享元模式要求可以共享的對象必須是細粒度對象,所以它又稱爲輕量級模式(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網絡請求,敬請關注……

相關文章
相關標籤/搜索