Dagger2 Android應用:@Component和@Module

這部分會介紹一下DI的主要概念,包括Component,Module,但不涉及和Android有關的具體代碼。
目的是在剝離實際開發的情況下先建立Component的概念,因為在DI中這是它的最主要部分,而Component概念在Android開發經驗中是不存在的。
開始以下的內容之前請忘記所有我們學過的Android知識。。。

依賴

什么是依賴?
我的理解,依賴是一個對象的存在需要依附另外一個對象,或者說一個對象需要另外一個對象。
我們先在不使用DI的情況下描述一個具體事例,然后再用DI改造我們的代碼。
舉個例子,我們有個咖啡機(這個例子很多介紹Dagger2的地方都用到),就像在星巴克看到的那樣,一個咖啡機需要一個泵和一個加熱器才能工作

class CoffeeMachine {
  Pump pumper;
  Heater heater;

  public static Coffee makeCoffee(){...}
}

interface Pump {
  void pump();
}

interface Heater {
  void heat();
}

因為Pump和Heater可能有各種不同的實現方式,比如Heater有電子加熱,也有蒸汽加熱,所以這里以接口聲明,Pump我們就用虹吸吧,然后是具體的實現方式

class Thermosiphon implements Pump {
  @Override
  void pump() {
    //impl
  }
}
class EletricHeater implements Heater {
  @Override
  void heat() {
    //impl
  }
}

OK,現在我們來做咖啡了,在不使用DI的情況下,我們會在Machine中直接實例化對象,

class CoffeeMachine {
  Pump pumper;
  Heater heater;

  public CoffeeMachine() {
    this.pumper = new Thermosiphon();
    this.heater = new EletricHeater();
  }

  public static Coffee makeCoffee(){...}
}

咖啡機做咖啡的過程所需要的依賴就如上面的代碼描述的一樣,我們只要使用 CoffeeMachine.makeCoffee() 就可以做咖啡了。

依賴注入

然后我們引入DI的概念,來說明如何通過DI來解耦。
來看一張對比圖


iDI 重構前后對比

左邊是在不使用DI的情況下,Machine和Pump和Heater的關系圖,右邊是使用了DI的情況。
對于左邊,假設一種情況,老板某一天覺得得有個咖啡師在煮咖啡才顯逼格,導致我們的Heater需要引入一個Cooker作為構造參數,

class EletricHeater implements Heater {
  public EletricHeater(Cooker cooker) {
    ...
  }
}

此時發生了問題,我們必須要修改Machine中對它的實例化方法。這不是我們想要的,試想在一個大型項目里,一個類的構造方法發生變動,就需要修改所有有引用到的地方,工作量巨大。
應對這種情況,一般我們會構造一個Factory類來進行實例化,再把實例化后的Heater對象set進Machine,這么做就實現把依賴的類的實例化邏輯放到一個統一的地方,讓他們解耦。

class CoffeeMachine {
  Pump pumper;
  Heater heater;

  public CoffeeMachine(Pump pumper, Heater heater) {
    this.pumper = pumper;
    this.heater = heater;
  }
}

void main {
  CoffeeMachine machine = new CoffeeMachine(PumpFactory.getInstance().buildPumper(),
  HeaterFactory.getInstance().buildHeater());
}

代碼類似上面這樣,具體Factory的實現比較普通就不細寫了。

上面的代碼就是粗略的依賴注入了,Machine不知道或者不關心具體實例的生成,它只關心它依賴于這兩個類,而heater和pumper的實例都是先在別的地方實例化完了再注入到Machine中的。

雖然用Factory類可以解耦,但是取而代之的是我們還要維護具體的Factory代碼,還是有工作量的。
Dagger2幫我們做了Factory代碼這部分,所有的模板代碼都可以直接在編譯期生成,我們只需要維護一份接口代碼,用來描述各個類的依賴關系就行。

Component + Module

現在,以上面的場景為例子來說明這兩個東西的概念。
Component,中文可以理解為組件,比如聊天組件,數據組件,或者是Presenter。
Module,中文可以理解為模塊,是提供功能給Component使用的一種存在。
以CoffeeMachine為例。這里的Component可以定義為咖啡機,而Heater和Pumper可以定義為Module。
下面用代碼來描述他們的依賴關系
首先定義Module

@Module
public class HeaterModule {

  @Provides
  public Heater provideHeater() {
    return new EletricHeater();//此時老板還沒改需求
  }
}

@Module
public class PumperModule {

  @Provides
  public Heater providePumper() {
    return new Thermosiphon();
  }
}

然后是Component

@Component {modules = {HeaterModule.class, PumperModule.class}}
public interface MachineComponent {
  void inject(CoffeeMachine machine);
}

用這三個類/接口的代碼,就描述了他們之間的依賴關系。先不解釋這代碼的語法含義,接著,在定義好依賴關系后首先來看怎么在代碼中使用Dagger2注入對象。

class CoffeeMachine {
  @Inject Pump pumper;
  @Inject Heater heater;

  public CoffeeMachine() {
  }
  public void makeCoffee() {
       mPumper.pump();
       mHeater.heat();
   }
}

WTF?是的沒錯只需要用@Inject注解標注需要注入的對象就行了,Dagger2會負責所有的實例化和注入過程。

注意需要注入的對象不能聲明為 private,否則Dagger2會沒辦法注入,因為它不是用反射的方式注入的。

上面就是最簡單的Dagger2的使用場景了,這里面忽略了很多細節,只是把重點放在了Component和Module這兩個概念上。
通過Dagger2,把原本類和類之間的關系,用Component和Module來描述。Machine類也不再需要關注所依賴的Heater是怎么實例化的了,它只需要知道接口就可以使用。而類和類之間的依賴關系則由Component去解決,這部分依賴關系,可以放到一個獨立的package下面去維護。
剛接觸的開發者可能會覺得這不是更麻煩了嗎,代碼量比以前更多了,為什么要這么復雜?當然主要因為這是一個非常簡單的例子,假設是在大型的項目上,有幾百個類的情況下,各個模塊之間的關系在依賴描述下能非常清晰的理解,而且互相之間耦合很低,只需要修改接口文件就可以快速的替換具體的業務邏輯,在這樣的場景下用DI是非常劃算的。

總結

回到圖一,通過DI,現在類和類之間的耦合已經完全分離了。我們也大致了解了DI的概念,和Dagger2對Component和Module的定義。然而真正實現DI的核心代碼是Dagger2自動生產的,我們需要看這部分代碼,才能真正明白Dagger2的工作原理,還有更深層次的去理解Dagger2的核心高級用法,像@Scope,@SubComponent。對于大型項目來說,@Scope和@SubComponent是絕對需要熟悉的東西。包括原理和高級用法在接下來的幾篇里繼續描述。

最后編輯于
?著作權歸作者所有,轉載或內容合作請聯系作者
平臺聲明:文章內容(如有圖片或視頻亦包括在內)由作者上傳并發布,文章內容僅代表作者本人觀點,簡書系信息發布平臺,僅提供信息存儲服務。

推薦閱讀更多精彩內容