Retrofit 里用到的一些設(shè)計模式

設(shè)計模式是一套被反復(fù)使用诞外、多數(shù)人知曉的、經(jīng)過分類編目的灾票、代碼設(shè)計經(jīng)驗的總結(jié)峡谊。使用設(shè)計模式是為了可重用代碼、讓代碼更容易被他人理解刊苍、保證代碼可靠性既们、程序的重用性。根據(jù)其目的分為創(chuàng)建型 Creational正什、結(jié)構(gòu)型 Structual啥纸、行為型 Behavioral。

為了提高軟件系統(tǒng)的可維護(hù)性和可復(fù)用性婴氮,增加軟件的可擴展性和靈活性斯棒,開發(fā)時要盡量根據(jù) 7 條原則來開發(fā)程序,從而提高軟件開發(fā)效率主经、節(jié)約軟件開發(fā)成本和維護(hù)成本名船。分別是

  • 開閉原則(Open Closed Principle,OCP):軟件實體應(yīng)當(dāng)對擴展開放旨怠,對修改關(guān)閉(Software entities should be open for extension,but closed for modification)蜈块。

  • 里氏替換原則(Liskov Substitution Principle鉴腻,LSP):繼承必須確保超類所擁有的性質(zhì)在子類中仍然成立(Inheritance should ensure that any property proved about supertype objects also holds for subtype objects)。里氏替換原則主要闡述了有關(guān)繼承的一些原則百揭,也就是什么時候應(yīng)該使用繼承爽哎,什么時候不應(yīng)該使用繼承,以及其中蘊含的原理器一。里氏替換原是繼承復(fù)用的基礎(chǔ)课锌,它反映了基類與子類之間的關(guān)系,是對開閉原則的補充,是對實現(xiàn)抽象化的具體步驟的規(guī)范渺贤。

  • 依賴倒置原則(Dependence Inversion Principle雏胃,DIP):高層模塊不應(yīng)該依賴低層模塊,兩者都應(yīng)該依賴其抽象志鞍;抽象不應(yīng)該依賴細(xì)節(jié)瞭亮,細(xì)節(jié)應(yīng)該依賴抽象(High level modules shouldnot depend upon low level modules.Both should depend upon abstractions.Abstractions should not depend upon details. Details should depend upon abstractions)。其核心思想是:要面向接口編程固棚,不要面向?qū)崿F(xiàn)編程统翩。依賴倒置原則是實現(xiàn)開閉原則的重要途徑之一,降低了客戶與實現(xiàn)模塊之間的耦合此洲。

  • 單一職責(zé)原則(Single Responsibility Principle厂汗,SRP):這里的職責(zé)是指類變化的原因,單一職責(zé)原則規(guī)定一個類應(yīng)該有且僅有一個引起它變化的原因呜师,否則類應(yīng)該被拆分(There should never be more than one reason for a class to change)娶桦。對象不應(yīng)該承擔(dān)太多職責(zé)。

  • 接口隔離原則(Interface Segregation Principle匣掸,ISP):客戶端不應(yīng)該被迫依賴于它不使用的方法(Clients should not be forced to depend on methods they do not use)趟紊。該原則還有另外一個定義:一個類對另一個類的依賴應(yīng)該建立在最小的接口上(The dependency of one class to another one should depend on the smallest possible interface)。
    以上兩個定義的含義是:要為各個類建立它們需要的專用接口碰酝,而不要試圖去建立一個很龐大的接口供所有依賴它的類去調(diào)用霎匈。
    接口隔離原則和單一職責(zé)都是為了提高類的內(nèi)聚性、降低它們之間的耦合性送爸,體現(xiàn)了封裝的思想铛嘱。

  • 迪米特法則(Law of Demeter,LoD):只與你的直接朋友交談袭厂,不跟“陌生人”說話(Talk only to your immediate friends and not to strangers)墨吓。其含義是:如果兩個軟件實體無須直接通信,那么就不應(yīng)當(dāng)發(fā)生直接的相互調(diào)用纹磺,可以通過第三方轉(zhuǎn)發(fā)該調(diào)用帖烘。其目的是降低類之間的耦合度,提高模塊的相對獨立性橄杨。
    “朋友”是指:當(dāng)前對象本身秘症、當(dāng)前對象的成員對象、當(dāng)前對象所創(chuàng)建的對象式矫、當(dāng)前對象的方法參數(shù)等乡摹,這些對象同當(dāng)前對象存在關(guān)聯(lián)、聚合或組合關(guān)系采转,可以直接訪問這些對象的方法聪廉。

  • 合成復(fù)用原則(Composite Reuse Principle,CRP):要求在軟件復(fù)用時,要盡量先使用組合或者聚合等關(guān)聯(lián)關(guān)系來實現(xiàn)板熊,其次才考慮使用繼承關(guān)系來實現(xiàn)框全。如果要使用繼承關(guān)系,則必須嚴(yán)格遵循里氏替換原則邻邮。合成復(fù)用原則同里氏替換原則相輔相成的竣况,兩者都是開閉原則的具體實現(xiàn)規(guī)范。

Retrofit

Retrofit 是一個類型安全的 Http 客戶端筒严,主要有以下功能特點

  1. 將 Http 請求對象化丹泉,函數(shù)化。讓接口的函數(shù)代表具體請求鸭蛙。
  2. 利用注解的方式標(biāo)記參數(shù)摹恨,將 HTTP 的請求方法,請求頭娶视,請求參數(shù)晒哄,請求體等等都用注解的方式標(biāo)記,使用起來非常方便肪获。
  3. 支持 Multipart寝凌,以及文件上傳(file upload)。
  4. 直接將 Http 的 Response 轉(zhuǎn)換成對象孝赫。用戶可以根據(jù) Response 的具體內(nèi)容较木,更換轉(zhuǎn)換器,或者自己新建轉(zhuǎn)化器青柄。
  5. Retrofit 默認(rèn)使用 OkHttp 開源庫請求后臺伐债,用戶也可以使用自定義的具體請求方式。方便擴展致开。
  6. 自帶提供了異步處理 Http 請求的方式峰锁。

我們結(jié)合 Retrofit 來看看他使用了什么設(shè)計模式,是如何實現(xiàn)解耦的双戳。

先來看看 Retrofit 的使用流程:

Retrofit adapts a Java interface to HTTP calls by using annotations on the declared methods to
define how requests are made. Create instances using the builder and pass your interface to create to generate an implementation.

將HTTP請求抽象成java接口類虹蒋,用注解描述和配置網(wǎng)絡(luò)請求參數(shù),封裝Url地址和網(wǎng)絡(luò)數(shù)據(jù)請求飒货。通過new Retrofit.Builder().build() 構(gòu)建 Retrofit 實例對象千诬,同時進(jìn)行具體配置。

public interface GitHubService {
  @GET("users/{user}/repos")
  Call<List<Repo>> listRepos(@Path("user") String user);
}

Create an implementation of the API defined by the service interface.

Retrofit.create() 返回了一個 Service Interface 的 proxy膏斤。

Retrofit retrofit = new Retrofit.Builder()
    .baseUrl("https://api.github.com/")
    .build();

GitHubService service = retrofit.create(GitHubService.class);

The Retrofit class generates an implementation of the GitHubService interface.Each Call from the created GitHubService can make a synchronous or asynchronous HTTP request to the remote webserver.

Call<List<Repo>> repos = service.listRepos("octocat");

網(wǎng)絡(luò)請求流程:

  1. 通過解析網(wǎng)絡(luò)請求接口的注解配置網(wǎng)絡(luò)請求參數(shù)

  2. 通過動態(tài)代理生成網(wǎng)絡(luò)請求對象

  3. 通過網(wǎng)絡(luò)請求適配器網(wǎng)絡(luò)請求對象進(jìn)行平臺適配

  4. 通過網(wǎng)絡(luò)請求執(zhí)行器發(fā)送網(wǎng)絡(luò)請求

  5. 通過數(shù)據(jù)轉(zhuǎn)換器解析服務(wù)器返回的數(shù)據(jù)

  6. 通過回調(diào)執(zhí)行器切換線程(子線程 ->>主線程)

  7. 用戶在主線程處理返回結(jié)果

這是別人畫的一張圖,基本都標(biāo)出來了邪驮,簡單的工廠莫辨、建造者、單例沒有標(biāo)。

網(wǎng)上大佬畫的圖

外觀模式 Facade

定義:

系統(tǒng)外部與一個子系統(tǒng)的通信必須通過一個統(tǒng)一的外觀對象進(jìn)行沮榜,為子系統(tǒng)中的一組接口提供一個一致的界面盘榨。外觀模式定義了一個高層接口,這個接口使得這一子系統(tǒng)更加容易使用蟆融。外觀模式又稱為門面模式草巡,它是一種對象結(jié)構(gòu)型模式。

Retrofit 對客戶端模塊提供統(tǒng)一接口型酥,Retrofit 類內(nèi)部封裝了 ServiceMethod山憨、CallAdapter 和 Converter 等組件。并且 CallAdapter 和 Converter 都是抽象為接口弥喉,用戶可以擴展自定義的實現(xiàn)郁竟。

public final class Retrofit {
  private final Map<Method, ServiceMethod<?, ?>> serviceMethodCache = new ConcurrentHashMap<>();

  final okhttp3.Call.Factory callFactory;
  final HttpUrl baseUrl;
  final List<Converter.Factory> converterFactories;
  final List<CallAdapter.Factory> callAdapterFactories;
  final @Nullable Executor callbackExecutor;
  final boolean validateEagerly;

  Retrofit(okhttp3.Call.Factory callFactory, HttpUrl baseUrl,
      List<Converter.Factory> converterFactories, List<CallAdapter.Factory> callAdapterFactories,
      @Nullable Executor callbackExecutor, boolean validateEagerly) {
    this.callFactory = callFactory;
    this.baseUrl = baseUrl;
    this.converterFactories = converterFactories; // Copy+unmodifiable at call site.
    this.callAdapterFactories = callAdapterFactories; // Copy+unmodifiable at call site.
    this.callbackExecutor = callbackExecutor;
    this.validateEagerly = validateEagerly;
  }
    /*...*/
}

動態(tài)代理 Proxy

代理模式是結(jié)構(gòu)型模式。當(dāng)無法或不想直接訪問某個對象由境,或者訪問某個對象比較復(fù)雜的時候棚亩,可以通過一個代理對象來間接訪問,代理對象向客戶端提供和真實對象同樣的接口功能虏杰。經(jīng)典設(shè)計模式中讥蟆,代理模式有四種角色:

  • Subject 抽象主題類——申明代理對象和真實對象共同的接口方法;
  • RealSubject 真實主題類——實現(xiàn)了 Subject 接口纺阔,真實執(zhí)行業(yè)務(wù)邏輯的地方瘸彤;
  • ProxySubject 代理類——實現(xiàn)了 Subject 接口,持有對 RealSubject 的引用州弟,在實現(xiàn)的接口方法中調(diào)用 RealSubject 中相應(yīng)的方法執(zhí)行钧栖;
  • Cliect 客戶端類——使用代理對象的類。
public class Client
{
    public static void main(String[] args)
    {
        ProxySubject proxy=new ProxySubject();
        proxy.request();
    }
}
//抽象主題
interface Subject
{
    void request();
}
//真實主題
class RealSubject implements Subject
{
    public void request()
    {
        System.out.println("訪問真實主題方法...");
    }
}
//代理
class ProxySubject implements Subject
{
    private RealSubject realSubject;
    public void request()
    {
        if (realSubject==null)
        {
            realSubject=new RealSubject();
        }
        preRequest();
        realSubject.request();
        postRequest();
    }
    public void preRequest()
    {
        System.out.println("訪問真實主題之前的預(yù)處理婆翔。");
    }
    public void postRequest()
    {
        System.out.println("訪問真實主題之后的后續(xù)處理拯杠。");
    }
}

代理模式分為靜態(tài)代理動態(tài)代理,嚴(yán)格按照上述角色定義編寫的代碼屬于靜態(tài)代理啃奴,即在代碼運行前 ProxySubject 代理類的 .class 編譯文件就已存在潭陪。Retrofit使用的是動態(tài)代理,是通過反射機制來動態(tài)生成方法接口的代理對象的最蕾。動態(tài)代理的實現(xiàn)是通過 JDK 提供的 InvocationHandler 接口依溯,實現(xiàn)該接口重寫其調(diào)用方法 invoke。

反射機制是在運行狀態(tài)中瘟则,對于任意一個類黎炉,都能夠知道這個類的所有屬性和方法;對于任意一個對象醋拧,都能夠調(diào)用它的任意一個方法和屬性慷嗜;這種動態(tài)獲取的信息以及動態(tài)調(diào)用對象的方法的功能稱為 java 語言的反射機制淀弹。

先看老師給的示例代碼()

定義 Isubject 接口,里面有兩個方法

public interface ISubject {
   public void request();
   public void output();
}

RealSubject 實現(xiàn)接口庆械,是真實主題

public class RealSubject implements ISubject {

   @Override
   public void request() {
      System.out.println("從房主處租房");

   }
   
   public void output(){
      try {
         Thread.sleep(100);
         System.out.println("裝修");
      } catch (InterruptedException e) {
         // TODO Auto-generated catch block
         e.printStackTrace();
      }
   }

}

動態(tài)代理類薇溃,實現(xiàn) InvocationHandler 接口,重寫 invoke 方法中體現(xiàn)了代理類的工作

/**
 * 該代理類的內(nèi)部屬性是Object類型缭乘,實際使用的時候通過該類的構(gòu)造方法傳遞進(jìn)來一個對象
 * 此外沐序,該類還實現(xiàn)了invoke方法,該方法中的method.invoke其實就是調(diào)用被代理對象的將來執(zhí)行的方法堕绩,
 * 方法參數(shù)是obj,表示該方法從屬于obj策幼,通過動態(tài)代理類,我們可以在執(zhí)行真是對象的方法前后加入額外的一些方法
 * @author Zan Wang
 *
 */
public class DynamicSubject implements InvocationHandler {
   private Object obj;
   Date t1, t2;
   long dateDiff;
   
   public DynamicSubject(Object obj) {
      this.obj = obj;
   } 

// Object proxy:代理對象
// Method method:目標(biāo)類要執(zhí)行的方法逛尚,
// Object[] args:方法中的參數(shù)
   @Override
   public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
      t1 = new Date(System.currentTimeMillis());
      System.out.println(method.getName() + "操作前的時間: " + new SimpleDateFormat("yyyy/MM/dd-HH:mm:ss:SSS").format(t1));
      
      method.invoke(obj, args);//真實主題的工作
      
      t2 = new Date(System.currentTimeMillis());
      System.out.println(method.getName() + "操作后的時間: " + new SimpleDateFormat("yyyy/MM/dd-HH:mm:ss:SSS").format(t2));
      
      dateDiff = t2.getTime() - t1.getTime(); 
      System.out.println(dateDiff + "毫秒完成了操作");
      return null;
   }

}
public class DynamicClient {

   public static void main(String[] args) {
      System.getProperties().put("sun.misc.ProxyGenerator.saveGeneratedFiles", "true");
      System.out.println(System.getProperties().get("sun.misc.ProxyGenerator.saveGeneratedFiles"));
      //真實主題 
      RealSubject realSubject = new RealSubject();
       //動態(tài)代理
      InvocationHandler handler = new DynamicSubject(realSubject);
       //獲得類信息
      Class<?> clazz = handler.getClass();
      System.out.println("$Proxy0.class全名: "+Proxy.getProxyClass(clazz.getClassLoader(), realSubject.getClass().getInterfaces()));
      
      //下面的代碼一次性生成代理
      ISubject subject = (ISubject)Proxy.newProxyInstance(clazz.getClassLoader(), 
            realSubject.getClass().getInterfaces(), handler);
      subject.request();
      subject.output();
      System.out.println(subject.getClass());
   }

}

再看看 Retrofit 的代碼

public <T> T create(final Class<T> service) {
  Utils.validateServiceInterface(service);
  if (validateEagerly) {
    eagerlyValidateMethods(service);
  }
    //返回動態(tài)代理類
  return (T) Proxy.newProxyInstance(service.getClassLoader(), new Class<?>[] { service },
      new InvocationHandler() {
        private final Platform platform = Platform.get();

          //處理 method 分為三種情況:Object 的方法垄惧,直接返回 Object 的實現(xiàn);判斷是否 Java8 支持的 DefaultMethod绰寞;或創(chuàng)建 OkHttpCall到逊,通過 ServiceMethod 轉(zhuǎn)換為接口的動態(tài)代理類。
        @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.adapt(okHttpCall);
        }
      });
}

使用 Retrofit 的客戶端通過 create 方法獲取自定義 HTTP 請求的動態(tài)代理類滤钱,是客戶端代碼中最重要的部分之一觉壶。這里有三個重要組件:

  • ServiceMethod
  • OkHttpCall
  • ServiceMethod.callAdapter

通過 ServiceMethod 來解析 method,通過解析注解件缸、參數(shù)铜靶,將它們封裝成我們所熟悉的 request。然后通過具體的返回值類型他炊,讓之前配置的工廠生成具體的 CallAdapter争剿、ResponseConverter。得到這個 ServiceMethod 之后痊末,傳給 OkHttpCall蚕苇,這個 OkHttpCall 就是對 Okhttp 的網(wǎng)絡(luò)請求封裝的一個類。用它跟 OkHttp 對接凿叠,所有 OkHttp 需要的參數(shù)都可以看這個類涩笤。serviceMethod.callAdapter.adapt(okHttpCall) 將 OkHttp 返回的CALL適配成應(yīng)當(dāng)返回的類型,默認(rèn)返回盒件。ServiceMethod 中如果沒有配置 CallAdapter蹬碧,則使用默認(rèn)的 DefaultCallAdapterFactory, 得到的結(jié)果是 Call<?>。

Retrofit 采用的代理模式和正常的代理模式并不一樣炒刁,正常的代理模式是對真實對象的一層控制恩沽,這個真實對象是實現(xiàn)對應(yīng)的接口的,而這里并沒有真實的對象翔始,只是拿到網(wǎng)絡(luò)請求接口實例上所有注解飒筑,它把方法調(diào)用最終全部轉(zhuǎn)發(fā)到 OkHttp 了片吊,更加靈活。

適配器模式 Adapter

適配器模式把一個類的接口變換成客戶端所期待的另一種接口协屡,從而使原本因接口不匹配而無法在一起工作的兩個類能夠在一起工作。適配器提供客戶類需要的接口全谤,適配器的實現(xiàn)就是把客戶類的請求轉(zhuǎn)化為對適配者的相應(yīng)接口的調(diào)用肤晓。也就是說:當(dāng)客戶類調(diào)用適配器的方法時,在適配器類的內(nèi)部將調(diào)用適配者類的方法认然,而這個過程對客戶類是透明的补憾,客戶類并不直接訪問適配者類。因此卷员,適配器可以使由于接口不兼容而不能交互的類可以一起工作盈匾。這就是適配器模式的模式動機。

  • Target:目標(biāo)抽象類
  • Adapter:適配器類
  • Adaptee:適配者類
  • Client:客戶端類

適配器模式有類的適配器模式對象的適配器模式兩種不同的形式毕骡。對象的適配器模式把被適配的類的 API 轉(zhuǎn)換成為目標(biāo)類的 API削饵,與類的適配器模式不同的是,對象的適配器模式不是使用繼承關(guān)系連接到 Adaptee 類未巫,而是使用委派關(guān)系連接到 Adaptee 類窿撬。

UML 圖

從上圖可以看出,Adaptee 類并沒有 sampleOperation2() 方法叙凡,而客戶端則期待這個方法劈伴。為使客戶端能夠使用 Adaptee 類,需要提供一個包裝 (Wrapper) 類 Adapter握爷。這個包裝類包裝了一個 Adaptee 的實例跛璧,從而此包裝類能夠把 Adaptee 的 API 與 Target 類的 API 銜接起來。Adapter 與 Adaptee 是委派關(guān)系新啼,這決定了適配器模式是對象的追城。

public interface Target {
    /**
     * 這是源類 Adaptee 也有的方法
     */
    public void sampleOperation1(); 
    /**
     * 這是源類 Adapteee 沒有的方法
     */
    public void sampleOperation2(); 
}
public class Adaptee {

    public void sampleOperation1(){}

}
public class Adapter {
    private Adaptee adaptee;

    public Adapter(Adaptee adaptee){
        this.adaptee = adaptee;
    }
    /**
     * 源類 Adaptee 有方法 sampleOperation1
     * 因此適配器類直接委派即可
     */
    public void sampleOperation1(){
        this.adaptee.sampleOperation1();
    }
    /**
     * 源類 Adaptee 沒有方法 sampleOperation2
     * 因此由適配器類需要補充此方法
     */
    public void sampleOperation2(){
        //寫相關(guān)的代碼
    }
}

類的適配器:

public class Adapter extends Adaptee implements Target {
    /**
     * 由于源類 Adaptee 沒有方法 sampleOperation2()
     * 因此適配器補充上這個方法
     */
    @Override
    public void sampleOperation2() {
        //寫相關(guān)的代碼
    }
}

那么看看 Retrofit 中的 CallAdapter 的接口。這里的 Call 是 OkHttpCall师抄。即讓已經(jīng)存在的OkHttpCall漓柑,被不同的標(biāo)準(zhǔn)、平臺來調(diào)用叨吮。設(shè)計了這個接口CallAdapter辆布,讓其他平臺做不同的實現(xiàn)來轉(zhuǎn)換。

/**
 * Adapts a {@link Call} with response type {@code R} into the type of {@code T}. Instances are
 * created by {@linkplain Factory a factory} which is
 * {@linkplain Retrofit.Builder#addCallAdapterFactory(Factory) installed} into the {@link Retrofit}
 * instance.
 */
public interface CallAdapter<R, T> {

  Type responseType();

  T adapt(Call<R> call);

  /**
   * Creates {@link CallAdapter} instances based on the return type of {@linkplain
   * Retrofit#create(Class) the service interface} methods.
   */
  abstract class Factory {

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

    protected static Type getParameterUpperBound(int index, ParameterizedType type) {
      return Utils.getParameterUpperBound(index, type);
    }

    protected static Class<?> getRawType(Type type) {
      return Utils.getRawType(type);
    }
  }
}

Call 接口是 Retrofit 內(nèi)置的發(fā)送請求給服務(wù)器并且返回響應(yīng)體的調(diào)用接口茶鉴,包括同步锋玲、異步請求,查詢涵叮、取消惭蹂、復(fù)制等功能伞插。

public interface Call<T> extends Cloneable {
    // 同步執(zhí)行請求
    Response<T> execute() throws IOException;
    // 異步執(zhí)行請求
    void enqueue(Callback<T> callback);
    // 省略代碼

    // 取消請求
    void cancel();
    // 復(fù)制請求
    Call<T> clone();
}

如果客戶端沒有配置 CallAdapter,Retrofit會采用默認(rèn)的實現(xiàn) DefaultCallAdapterFactory 直接返回Call對象盾碗,而如果配置了 RxJava 的 RxJavaCallAdapterFactory 實現(xiàn)媚污,就會將 Call<R> 轉(zhuǎn)換為 Observable<R>,供客戶端調(diào)用廷雅。

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;
    }
  }

總結(jié)下耗美,CallAdapter 對應(yīng) Target,其 adapt 方法返回客戶端類 Client 需要的對象航缀;RxJavaCallAdapterFactory 的 get 方法返回 SimpleCallAdapter 對象(或 ResultCallAdapter 對象)實現(xiàn)了 CallAdapter<Observable<?>>商架,對應(yīng) Adapter;Call <R>對應(yīng) Adaptee 適配者類芥玉,包含需要被適配的方法蛇摸。

值得說明的是,這里 SimpleCallAdapter 并沒有通過域的方式持有Call<R>灿巧,而是直接在 CallAdapter 的 get 方法中將 Call<R> 以入?yún)⑿问絺魅敫习馈km然并不是教科書式的對象適配器模式,但使用卻更加靈活砸烦、方便弃鸦。

工廠模式 Factory

這里把簡單工廠模式、工廠模式和抽象工廠模式放一起了幢痘,它們都屬于創(chuàng)建型模式唬格,其主要功能都是將對象的實例化部分抽取出來。

簡單工廠模式

只需要傳給工廠一些參數(shù)信息颜说,工廠解析參數(shù)返回相應(yīng)的產(chǎn)品购岗。對外隱藏產(chǎn)品細(xì)節(jié),邏輯簡單门粪。

以 Platform 類為例喊积,其首先包含靜態(tài)域 PLATFORM,并通過靜態(tài)返回供客戶端調(diào)用玄妈。

private static final Platform PLATFORM = findPlatform();

static Platform get() {
    return PLATFORM;
}

findPlatform 其實就是一個靜態(tài)工廠方法蜓洪,根據(jù) Class.forName 是否拋出 ClassNotFoundException 來判斷不同的平臺谆棱。

private static Platform findPlatform() {
    try {
        Class.forName("android.os.Build");
        if (Build.VERSION.SDK_INT != 0) {
            return new Android();
        }
    } catch (ClassNotFoundException ignored) {
    }
    try {
        Class.forName("java.util.Optional");
        return new Java8();
    } catch (ClassNotFoundException ignored) {
    }
    try {
        Class.forName("org.robovm.apple.foundation.NSObject");
        return new IOS();
    } catch (ClassNotFoundException ignored) {
    }
    return new Platform();
}

而Android追他、Java8震捣、IOS 相當(dāng)于 ConcreteProduct 的角色,繼承自抽象產(chǎn)品類Platform酝锅。

Java8:

static class Java8 extends Platform {}

Android:

static class Android extends Platform {}

IOS:

static class IOS extends Platform {}

工廠模式

工廠模式對簡單工廠中的工廠類進(jìn)行了抽象诡必。一個工廠只生產(chǎn)一種產(chǎn)品,所有的工廠都實現(xiàn)同一個抽象接口搔扁。工廠可以自主確定創(chuàng)建何種產(chǎn)品爸舒;符合開閉原則蟋字,增加新產(chǎn)品時,只需增加新類扭勉。

還是 CallAdapter 的代碼鹊奖,CallAdapter.Factory 對應(yīng)Factory抽象工廠類,包含兩個靜態(tài)工具方法 getParameterUpperBound涂炎、getRawType 和抽象方法 get嫉入。

public interface CallAdapter<T> {

    Type responseType();
    <R> T adapt(Call<R> call);
 
    abstract class Factory {
        public abstract CallAdapter<?> get(Type returnType, Annotation[] annotations, Retrofit retrofit);

        protected static Type getParameterUpperBound(int index, ParameterizedType type) {
            return Utils.getParameterUpperBound(index, type);
        }

        protected static Class<?> getRawType(Type type) {
            return Utils.getRawType(type);
        }
    }
}

get 方法返回不同類型的 CallAdapter,比如 RxJavaCallAdapterFactory 返回 CallAdapter<Observable<?>>璧尸,DefaultCallAdapterFactory 返回 CallAdapter<Call<?>>。

public final class RxJavaCallAdapterFactory extends CallAdapter.Factory {

    // 省略代碼
    @Override
    public CallAdapter<?> get(Type returnType, Annotation[] annotations, Retrofit retrofit) {
    
        // 省略代碼
        CallAdapter<Observable<?>> callAdapter = getCallAdapter(returnType, scheduler);
        // 省略代碼
        return callAdapter;
    }

    private CallAdapter<Observable<?>> getCallAdapter(Type returnType, Scheduler scheduler) {
        // 省略代碼
    }
}

如果需要增加新的 CallAdapter熬拒,繼承自 CallAdapter.Factory爷光,覆蓋 get 方法即可。符合面向?qū)ο筌浖O(shè)計的“開閉原則”澎粟。

抽象工廠模式

配置Retrofit時還需要配置 ConverterFactory蛀序,調(diào)用 OkHttp 時,將請求內(nèi)容由T轉(zhuǎn)換為 okhttp3.RequestBody活烙,將返回內(nèi)容由 okhttp3.ResponseBody 轉(zhuǎn)換為 T徐裸,Converter 是就是負(fù)責(zé)轉(zhuǎn)換的類。Retrofit官方文檔中就給出了多種不同實現(xiàn)的轉(zhuǎn)換器類啸盏,均繼承自 Converter.Factory重贺。

相比于工廠模式,具體工廠負(fù)責(zé)生產(chǎn)具體的產(chǎn)品回懦,每一個具體工廠對應(yīng)一種具體產(chǎn)品气笙,工廠方法也具有唯一性,一般情況下怯晕,一個具體工廠中只有一個工廠方法或者一組重載的工廠方法潜圃。但是有時候我們需要一個工廠可以提供多個產(chǎn)品對象,而不是單一的產(chǎn)品對象舟茶,例如上述 Converter.Factory 需要同時提供請求內(nèi)容和返回內(nèi)容的轉(zhuǎn)換類谭期,這時,就需要考慮抽象工廠模式吧凉。一個工廠只生產(chǎn)一種產(chǎn)品隧出,不同的工廠可能實現(xiàn)不同的抽象工廠接口。

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

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

        public Converter<?, RequestBody> requestBodyConverter(Type type, 
               Annotation[] parameterAnnotations, Annotation[] methodAnnotations, Retrofit retrofit) {
            return null;
        }

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

以 GsonConverterFactory 為例進(jìn)行說明客燕,GsonConverterFactory 對應(yīng) ConcreteFactory 具體工廠鸳劳,表示 Gson 轉(zhuǎn)換類的工廠,GsonConverterFactory 繼承自 AbstractFactory 抽象工廠——Converter.Factory也搓,重寫了 requestBodyConverter 方法和 responseBodyConverter 方法赏廓。

public final class GsonConverterFactory extends Converter.Factory {
    // 省略代碼
    @Override
    public Converter<ResponseBody, ?> responseBodyConverter(Type type, Annotation[] annotations,
        Retrofit retrofit) {
        TypeAdapter<?> adapter = gson.getAdapter(TypeToken.get(type));
        return new GsonResponseBodyConverter<>(gson, adapter);
    }

    @Override
    public Converter<?, RequestBody> requestBodyConverter(Type type,
        Annotation[] parameterAnnotations, Annotation[] methodAnnotations, Retrofit retrofit) {
        TypeAdapter<?> adapter = gson.getAdapter(TypeToken.get(type));
        return new GsonRequestBodyConverter<>(gson, adapter);
    }
}

策略模式 Strategy

完成一項任務(wù)涵紊,往往可以有多種不同的方式,每一種方式稱為一個策略幔摸,我們可以根據(jù)環(huán)境或者條件的不同選擇不同的策略來完成該項任務(wù)摸柄。針對這種情況,一種常規(guī)的做法是將多個策略寫在一個類中既忆,通過 if…else 或者 switch 等條件判斷語句來選擇具體的算法驱负。這種方式實現(xiàn)簡單、快捷患雇,但維護(hù)成本很高跃脊,當(dāng)添加新的策略時,需要修改源代碼苛吱,這違背了開閉原則和單一原則酪术。仍以 CallAdapter 為例,不同的 CallAdapter 代表著不同的策略翠储,當(dāng)我們調(diào)用這些不同的適配器的方法時绘雁,就能得到不同的結(jié)果,這就是策略模式援所。策略模式包含三種角色:

  • Context 上下文環(huán)境——區(qū)別于 Android 的 Context庐舟,這里代表操作策略的上下文;
  • Stragety 抽象策略——即不同策略需要實現(xiàn)的方法住拭;
  • ConcreteStragety 策略實現(xiàn)——實現(xiàn) Stragety 抽象策略挪略。

在 Retrofit中,配置 Retrofit.Builder 時 addCallAdapterFactory废酷,配置的類就對應(yīng) Context瘟檩;不同的 CallAdapter 都需要提供 adapt 方法,CallAdapter<T> 就對應(yīng) Stragety 抽象策略澈蟆。RxJavaCallAdapterFactory 的 get 方法返回 SimpleCallAdapter 對象(或 ResultCallAdapter 對象)就對應(yīng)具體的策略實現(xiàn)墨辛。

通過 get 方法返回不同的 CallAdapter 對象;強調(diào)這些不同 CallAdapter 對象的 adapt 方法的具體實現(xiàn)趴俘。

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

工廠模式強調(diào)的是生產(chǎn)不同的對象睹簇,策略模式強調(diào)的是這些不同對象的策略方法的具體實現(xiàn),是在創(chuàng)建對象之后寥闪。

建造者模式 Builder

建造者模式屬于創(chuàng)建型模式太惠,將構(gòu)建復(fù)雜對象的過程和它的部件解耦,使構(gòu)建過程和部件的表示隔離疲憋。Retrofit 內(nèi)部包含 Retrofit.Builder凿渊,Retrofit 包含的域都能通過 Builder 進(jìn)行構(gòu)建。經(jīng)典設(shè)計模式中建造者模式有四種角色:

  • Product 產(chǎn)品類——該類為一般為抽象類,定義 Product 的公共屬性配置埃脏;
  • Builder 建造類——該類同樣為抽象類搪锣,規(guī)范 Product 的組建,一般由子類實現(xiàn)具體 Product 的構(gòu)建過程彩掐;
  • ConcreteBuilder 實際建造類——繼承自 Builder构舟,構(gòu)建具體的 Product;
  • Director 組裝類——統(tǒng)一組裝過程堵幽。

在 Retrofit 類中狗超,Retrofit 直接對應(yīng) Product,并沒有基于抽象 Product 進(jìn)行擴展朴下;Retrofit.Builder 對應(yīng) ConcreteBuilder努咐,也沒有基于抽象 Builder 進(jìn)行擴展,同時省略了 Director殴胧,并在 Retrofit.Builder 每個 setter 方法都返回自身麦撵,使得客戶端代碼可以鏈?zhǔn)秸{(diào)用,整個構(gòu)建過程更加簡單溃肪。

public static final class Builder {
  private final Platform platform;
  private @Nullable okhttp3.Call.Factory callFactory;
  private HttpUrl baseUrl;
  private final List<Converter.Factory> converterFactories = new ArrayList<>();
  private final List<CallAdapter.Factory> callAdapterFactories = new ArrayList<>();
  private @Nullable Executor callbackExecutor;
  private boolean validateEagerly;

  Builder(Platform platform) {
    this.platform = platform;
  }

  public Builder() {
    this(Platform.get());
  }

  Builder(Retrofit retrofit) {
    platform = Platform.get();
    callFactory = retrofit.callFactory;
    baseUrl = retrofit.baseUrl;

    converterFactories.addAll(retrofit.converterFactories);
    // Remove the default BuiltInConverters instance added by build().
    converterFactories.remove(0);

    callAdapterFactories.addAll(retrofit.callAdapterFactories);
    // Remove the default, platform-aware call adapter added by build().
    callAdapterFactories.remove(callAdapterFactories.size() - 1);

    callbackExecutor = retrofit.callbackExecutor;
    validateEagerly = retrofit.validateEagerly;
  }

  /**
   * Create the {@link Retrofit} instance using the configured values.
   * <p>
   * Note: If neither {@link #client} nor {@link #callFactory} is called a default {@link
   * OkHttpClient} will be created and used.
   */
  public Retrofit build() {
    if (baseUrl == null) {
      throw new IllegalStateException("Base URL required.");
    }

    okhttp3.Call.Factory callFactory = this.callFactory;
    if (callFactory == null) {
      callFactory = new OkHttpClient();
    }

    Executor callbackExecutor = this.callbackExecutor;
    if (callbackExecutor == null) {
      callbackExecutor = platform.defaultCallbackExecutor();
    }

    // Make a defensive copy of the adapters and add the default Call adapter.
    List<CallAdapter.Factory> callAdapterFactories = new ArrayList<>(this.callAdapterFactories);
    callAdapterFactories.add(platform.defaultCallAdapterFactory(callbackExecutor));

    // Make a defensive copy of the converters.
    List<Converter.Factory> converterFactories =
        new ArrayList<>(1 + this.converterFactories.size());

    // Add the built-in converter factory first. This prevents overriding its behavior but also
    // ensures correct behavior when using converters that consume all types.
    converterFactories.add(new BuiltInConverters());
    converterFactories.addAll(this.converterFactories);

    return new Retrofit(callFactory, baseUrl, unmodifiableList(converterFactories),
        unmodifiableList(callAdapterFactories), callbackExecutor, validateEagerly);
  }
}

裝飾模式 Decorator

裝飾者模式以對客戶透明的方式動態(tài)地給一個對象附加上更多的責(zé)任。換言之音五,客戶端并不會覺得對象在裝飾前和裝飾后有什么不同惫撰。裝飾者模式可以在不使用創(chuàng)造更多子類的情況下,將對象的功能加以擴展躺涝。屬于對象結(jié)構(gòu)型模式厨钻。

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;
  }
}

可以將ExecutorCallbackCall當(dāng)作是裝飾角色,而真正去執(zhí)行請求的是OkHttpCall坚嗜。之所以要有個裝飾類夯膀,是希望在原有類操作時去做一些額外操作。這里的操作就是線程轉(zhuǎn)換苍蔬,將子線程切換到主線程上去诱建。

觀察者模式 Observer

所有與網(wǎng)絡(luò)請求相關(guān)的庫一定會支持請求的異步發(fā)送,通過在庫內(nèi)部維護(hù)一個隊列碟绑,將請求添加到該隊列俺猿,同時注冊一個回調(diào)接口,以便執(zhí)行引擎完成該請求后格仲,將請求結(jié)果進(jìn)行回調(diào)押袍。Retrofit 的網(wǎng)絡(luò)請求執(zhí)行引擎是 OkHttp,請求類是 OkHttpCall凯肋,其實現(xiàn)了 Call 接口谊惭,enqueue 方法如下,入?yún)?Callback 對象。

void enqueue(Callback<T> callback);

在 OkHttpCall 的 enqueue 實現(xiàn)方法中圈盔,通過在 okhttp3.Callback() 的回調(diào)方法中調(diào)用上述入?yún)?Callback 對象的方法豹芯,實現(xiàn)通知觀察者。

@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();
        }
   }

總結(jié)下药磺,Call 接口對應(yīng) Subject告组,定義被觀察者的特性,包含 enqueue 等癌佩; OkHttpCall 對應(yīng) ConcreteSubject 具體被觀察者木缝,Callback 對應(yīng) Observer 抽象觀察者,Callback 的實現(xiàn)類對應(yīng) ConcreteObserver 具體觀察者围辙。

參考

《設(shè)計模式:可復(fù)用面向?qū)ο筌浖幕A(chǔ)》

http://www.reibang.com/p/fb8d21978e38

https://www.cnblogs.com/younghao/p/6078156.html

https://www.cnblogs.com/younghao/p/6098329.html

https://github.com/android-cn/android-open-project-analysis/tree/master/tool-lib/network/retrofit

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末我碟,一起剝皮案震驚了整個濱河市,隨后出現(xiàn)的幾起案子姚建,更是在濱河造成了極大的恐慌矫俺,老刑警劉巖,帶你破解...
    沈念sama閱讀 222,252評論 6 516
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件掸冤,死亡現(xiàn)場離奇詭異厘托,居然都是意外死亡,警方通過查閱死者的電腦和手機稿湿,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 94,886評論 3 399
  • 文/潘曉璐 我一進(jìn)店門铅匹,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人饺藤,你說我怎么就攤上這事包斑。” “怎么了涕俗?”我有些...
    開封第一講書人閱讀 168,814評論 0 361
  • 文/不壞的土叔 我叫張陵罗丰,是天一觀的道長。 經(jīng)常有香客問我再姑,道長萌抵,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 59,869評論 1 299
  • 正文 為了忘掉前任元镀,我火速辦了婚禮谜嫉,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘凹联。我一直安慰自己沐兰,他們只是感情好,可當(dāng)我...
    茶點故事閱讀 68,888評論 6 398
  • 文/花漫 我一把揭開白布蔽挠。 她就那樣靜靜地躺著住闯,像睡著了一般瓜浸。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上比原,一...
    開封第一講書人閱讀 52,475評論 1 312
  • 那天插佛,我揣著相機與錄音,去河邊找鬼量窘。 笑死雇寇,一個胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的蚌铜。 我是一名探鬼主播锨侯,決...
    沈念sama閱讀 41,010評論 3 422
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼冬殃!你這毒婦竟也來了囚痴?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 39,924評論 0 277
  • 序言:老撾萬榮一對情侶失蹤审葬,失蹤者是張志新(化名)和其女友劉穎深滚,沒想到半個月后,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體涣觉,經(jīng)...
    沈念sama閱讀 46,469評論 1 319
  • 正文 獨居荒郊野嶺守林人離奇死亡痴荐,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 38,552評論 3 342
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發(fā)現(xiàn)自己被綠了官册。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片蹬昌。...
    茶點故事閱讀 40,680評論 1 353
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖攀隔,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情栖榨,我是刑警寧澤昆汹,帶...
    沈念sama閱讀 36,362評論 5 351
  • 正文 年R本政府宣布,位于F島的核電站婴栽,受9級特大地震影響满粗,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜愚争,卻給世界環(huán)境...
    茶點故事閱讀 42,037評論 3 335
  • 文/蒙蒙 一映皆、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧轰枝,春花似錦捅彻、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 32,519評論 0 25
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至,卻和暖如春缭裆,著一層夾襖步出監(jiān)牢的瞬間键闺,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 33,621評論 1 274
  • 我被黑心中介騙來泰國打工澈驼, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留辛燥,地道東北人。 一個月前我還...
    沈念sama閱讀 49,099評論 3 378
  • 正文 我出身青樓缝其,卻偏偏與公主長得像挎塌,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子氏淑,可洞房花燭夜當(dāng)晚...
    茶點故事閱讀 45,691評論 2 361

推薦閱讀更多精彩內(nèi)容