Dagger2

DI 工具

什麼是 DI

DI, Dependency Injection (相依性注入) ,Anti-DIY, Auto-DIY 自動組裝,行前準備/著裝/組件/要件

例如 ButterKnife 就是一種 DI ,只是針對 findView 來做的 Injection 。

在特定的生命週期,幫你生成所需的物件。Dagger 是讓你創造自己的 Injection。

另外一個重點是,重用元件。

著名的咖啡機

CoffeeMaker

沖泡出一杯風味十足的咖啡之前,你需要一台濾泡式咖啡機。

需要的元件有:

  • 加熱器:把水加熱
  • 幫浦

在沒有的 DI 概念下:

Before:

CoffeMaker maker = new CoffeeMaker();
maker.brew(); // 沖泡
maker.brew(); // 沖泡
class CoffeeMaker { // 咖啡機
    private final Heater heater; // 加熱器
    private final Pump pump; // 幫浦

    CoffeeMaker() {
        this.heater = new ElectricHeater(); // 電熱器
        this.pump = new Thermosiphon(heater); // 虹吸幫浦(虹吸,剛好也需要加熱器)
    }

    public void brew() { /* ... */ }
}

缺點是每杯咖啡生產的整台咖啡機、電熱器與幫浦,這些組件都無法延用到其他裝置身上,太浪費了,一點都不環保。

手動 DI:

After:

Heater heater = new ElectricHeater();
Pump pump = new Thermosiphon(heater);
CoffeeMaker maker = new CoffeeMaker(heater, pump);
CoffeeMaker maker2 = new CoffeeMaker(heater, pump);
maker.brew();
maker2.brew();
class CoffeeMaker {
    private final Heater heater;
    private final Pump pump;

    CoffeeMaker(Heater heater, Pump pump) {
        this.heater = heater;
        this.pump = pump;
    }

    public void brew() { /* ... */ }
}

這樣至少電熱器與幫浦都可以有機會重覆拿給別人使用了。

但是這樣我們要泡咖啡前,都要自己準備電熱器與幫浦然後組裝成咖啡機後才開始泡咖啡。

我們希望寫好咖啡機所需要的組件,請一個組裝工人幫我們組 ,以後只要說我現在要用咖啡機就馬上組裝好了,我們都不用自己 DIY。其他裝置也是由這個工人負責組裝,這個工人可以沿用相同零組件來生產。

利用 Dagger2 自動 DI 來組裝那些要件,只要描述好要件相依後,就可以一直泡一直泡:

Coffee coffee = Dagger_CoffeeApp_Coffee.create();
Coffee coffee2 = Dagger_CoffeeApp_Coffee.create();
coffee.maker().brew(); // 一直泡
coffee2.maker().brew(); // 一直泡

咖啡機要加熱加壓沖泡,相依要件關係圖:

CoffeeMaker -> DripCoffeeModule -----------------------> Heater
                  \-> PumpModule -> ThermosiphonPump -/

濾泡式咖啡機需要把水加熱、加壓後沖泡:

class CoffeeMaker {
  private final Lazy<Heater> heater; // 加熱器
  private final Pump pump;

  // 準備幫浦與加熱器
  @Inject CoffeeMaker(Lazy<Heater> heater, Pump pump) {
    this.heater = heater;
    this.pump = pump;
  }

  // 沖泡
  public void brew() {
    heater.get().on(); // 加熱
    pump.pump(); // 加壓
    System.out.println(" [_]P coffee! [_]P "); // 熱騰騰的咖啡出爐囉!
    heater.get().off(); // 隨手關加熱器
  }
}

開始寫組裝說明書:

@Singleton // 共用咖啡機
@Component(modules = DripCoffeeModule.class) // 濾泡裝置(安裝著高壓熱水沖泡裝置提供幫浦與加熱器)
public interface Coffee {
    CoffeeMaker maker();
}

濾泡裝置需要幫浦加壓器具:

@Module(includes = PumpModule.class) // 一同準備加壓器具
class DripCoffeeModule { // 濾泡裝置
  @Provides @Singleton Heater provideHeater() { // 提供共用的加熱器具
    return new ElectricHeater(); // 電熱器具
  }
}

幫浦:

@Module(complete = false, library = true) // complete = false 需要借用加熱器具
class PumpModule { // 幫浦加壓器具
  @Provides Pump providePump(Thermosiphon pump) { // 利用熱虹吸管來提供幫浦能力
    return pump;
  }
}

熱虹吸管幫浦:

class Thermosiphon implements Pump {
  private final Heater heater;

  @Inject
  Thermosiphon(Heater heater) { // 索取加熱器來加壓
    this.heater = heater;
  }

  @Override public void pump() {
    if (heater.isHot()) {
      System.out.println("=> => pumping => =>");
    }
  }
}

動手玩

git clone https://github.com/yongjhih/dagger2-sample
cd dagger2-sample
./gradlew execute

範例 1

Before:

OkHttpClient client = new OkHttpClient();
TwitterApi api = new TwitterApi(client);

String user = "Andrew Chen";

Tweeter tweeter = new Tweeter(api, user);
tweeter.tweet("Hello, world!");

Timeline timeline = new Timeline(api, user);
for (Tweet tweet : timeline.get()) {
    System.out.println(tweet);
}

String user2 = "Andrew Chen2";

Tweeter tweeter2 = new Tweeter(api, user2);
tweeter.tweet("Hello, world!");

Timeline timeline2 = new Timeline(api, user2);
for (Tweet tweet : timeline.get()) {
    System.out.println(tweet);
}

隱藏相依前置作業,透過 builder 來獲得末端物件。

After:

ApiComponent apiComponent = Dagger_ApiComponent.create();

TwitterComponent twitterComponent = Dagger_TwitterComponent.builder()
    .apiComponent(apiComponent)
    .twitterModule(new TwitterModule("Andrew Chen"))
    .build();

Tweeter tweeter = twitterComponent.tweeter();
Timeliner timeline = twitterComponent.timeline();

for (Tweet tweet : timeline.get()) {
    System.out.println(tweet);
}

TwitterComponent component2 = Dagger_TwitterComponent.builder()
    .apiComponent(apiComponent)
    .twitterModule(new TwitterModule("Andrew Chen2"))
    .build();

Tweeter tweeter2 = component2.tweeter();
Timeliner timeline2 = component2.timeline();

for (Tweet tweet : timeline.get()) {
    System.out.println(tweet);
}

連帶修改:

@Module
public class NetworkModule {
    @Provides @Singleton // @Singleto 註明沿用同一個, @Provides 註明可提供 OkHttpClient
    OkHttpClient provideOkHttpClient() {
        return new OkHttpClient();
    }
}
@Singleton
public class TwitterApi {
    private final OkHttpClient client;

    @Inject // 註明由相依提供 OkHttpClient
    public TwitterApi(OkHttpClient client) {
        this.client = client;
    }
}
@Singleton
@Component(modules = NetworkModule.class) // 由哪些 modules 組成
public interface ApiComponent {
    TwitterApi api();
}
@Module
public class TwitterModule {
    private final String user;

    public TwitterModule(String user) {
        this.user = user;
    }

    @Provides
    Tweeter provideTweeter(TwitterApi api) {
        return new Tweeter(api, user);
    }

    @Provides
    Timeline provideTimeline(TweeterApi api) {
        return new Timeline(api, user);
    }
}
@Component(
    dependencies = ApiComponent.class,
    modules = TwitterModule.class
)
public interface TwitterComponent {
    Tweeter tweeter();
    Timeline timeline();
}

範例 2 - OkHttpClient

範例 3 - Facebook

範例 4 - AndroidUniversalImageLoader

範例 5 - Fresco

範例 5 - Picasso

範例 5 - Parse

See Also

results matching ""

    No results matching ""