PPAPで学ぶDaggerによるDI

Daggerってありますよね。コンパイル時に依存性を解決するのでパフォーマンス的に有利なDIコンテナです。
https://google.github.io/dagger/
依存関係の不備がコンパイル時にエラーになって発見できるのも、実行時にエラーが出たときの修正の難易度が高いAndroidアプリにはありがたいということで、Androidでよく使われてるようです。

基本的なオブジェクトの定義

I have a pen.

public class Pen {
    @Override
    public String toString() {
        return "ペン";
    }   
}


I have an apple.

public class Apple {
    @Override
    public String toString() {
        return "アッポー";
    }
}


Ohh!!! Apple Pen!!!

public class ApplePen {
    Pen pen;
    Apple apple;

    public ApplePen(Pen pen, Apple apple) {
        this.pen = pen;
        this.apple = apple;
    }

    @Override
    public String toString() {
        return "" + apple + pen;
    }
}

オブジェクトグラフの定義

Daggerでは、オブジェクトの生成を、@Moduleアノテーションがついたクラスの @Providesアノテーションがついたメソッドで定義します。

import dagger.Module;
import dagger.Provides;

@Module
public class Piko {
    
    @Provides Pen iHaveAPen() {
        return new Pen();
    }
    @Provides Apple iHaveAnApple() {
        return new Apple();
    }
    
    @Provides ApplePen applePen(Pen pen, Apple apple) {
        return new ApplePen(pen, apple);
    }
}


applePenメソッドはPenとAppleを引数として受け取りますが、これはDaggerが自動的に設定してくれます。
このようにして、依存関係を定義します。

Daggerでオブジェクト生成

Daggerにオブジェクト生成させることももちろんできます。その場合、コンストラクタにjavax.inject.Injectアノテーションをつけます。


I have pineapple.

import javax.inject.Inject;

public class Pineapple {

    @Inject
    public Pineapple() {
    }
    
    @Override
    public String toString() {
        return "パイナッポー";
    } 
}


Ohhh!!! Pineapple Pen!

import javax.inject.Inject;

public class PenPineapple {
    Pen pen;
    Pineapple pineapple;

    @Inject
    public PenPineapple(Pen pen, Pineapple pineapple) {
        this.pen = pen;
        this.pineapple = pineapple;
    }

    @Override
    public String toString() {
        return "" + pen + pineapple;
    }   
}


Ahhh!!! Pen Pineapple Apple Pen!!!

import javax.inject.Inject;

public class PenPineappleApplePen {
    PenPineapple penPineapple;
    ApplePen applePen;

    @Inject
    public PenPineappleApplePen(PenPineapple penPineapple, ApplePen applePen) {
        this.penPineapple = penPineapple;
        this.applePen = applePen;
    }

    @Override
    public String toString() {
        return "" + penPineapple + applePen;
    }   
}

コンポーネントの定義

Daggerによって組み立てられたオブジェクトを取得するために、コンポーネントを定義する必要があります。オブジェクトグラフのモジュールを指定した@Componentアノテーションをつけてinterfaceを定義して、その中に必要なオブジェクトを返すメソッドを定義します。

@Component(modules = Piko.class)
interface Taro {
    PenPineappleApplePen ppap();
}


モジュールとコンポーネントは対応することが多いので、モジュールクラスの中で定義すると便利なことが多いです。

import dagger.Component;
import dagger.Module;
import dagger.Provides;

@Module
public class Piko {
    
    @Component(modules = Piko.class)
    interface Taro {
        PenPineappleApplePen ppap();
    }
    
    @Provides Pen iHaveAPen() {
        return new Pen();
    }
    @Provides Apple iHaveAnApple() {
        return new Apple();
    }
    
    @Provides ApplePen applePen(Pen pen, Apple apple) {
        return new ApplePen(pen, apple);
    }
}

使ってみる

Daggerでは、コンパイル時にこのようなクラスが生成されます。

コンポーネントに対して、「Dagger」で始まるクラスが生成されています。内部インタフェースの場合は、「_」で区切られてインタフェース名が追加されます。今回は、Pikoクラスの中にTaroインタフェースを定義したので、「DaggerPiko_Taro」になってますね。


ここにBuilerクラスが定義されます。このBuilderクラスには、モジュールの名前のメソッドが定義されているので、そのメソッドを使ってモジュールを設定します。最後にbuildメソッドを呼び出すと、コンポーネントオブジェクトが返ってくるので、定義したメソッドを呼び出して組立済オブジェクトを取得します。

Piko.Taro pikotaro = DaggerPiko_Taro.builder()
        .piko(new Piko())
        .build();
PenPineappleApplePen ppap = pikotaro.ppap();


ということで、こんな感じになります。

public class PPAP {
    public static void main(String[] args) {
        Piko.Taro pikotaro = DaggerPiko_Taro.builder()
                .piko(new Piko())
                .build();
        PenPineappleApplePen ppap = pikotaro.ppap();
        System.out.println(ppap);
    }
}


実行すると、こう。