Dependency injection framework.
Dagger2
WHO?
• 松村 勇輝
• Twitter. @Yuki_312
• Yukiの枝折. http://yuki312.blogspot.jp/
• Android Developer
CONTENTS
• 依存性
• DI Framework
• Dagger2
• テストとアーキテクチャ
• 補足
ソフトウェア設計の課題
依存性
依存性
依存性
GitHubの情報を“保存する”アプリケーション
GitHub
<interface>
GitHubStore
依存性
GitHubの情報を“保存する”アプリケーション
GitHubStoreを使って“保存”を実現する
GitHub
<interface>
GitHubStore
依存性
implements
インタフェースはそのままでは使えない.
具象化した実装クラスが必要.
GitHubDatabase
GitHub
<interface>
GitHubStore
依存性
実装クラスはインスタンス化しなければならない.
GitHubDatabase
new
GitHub
<interface>
GitHubStore
依存性
実装クラスはインスタンス化しなければならない.
class GitHub {
private GitHubStore store = new GitHubDatabase();
}
GitHubDatabase
new
GitHub
<interface>
GitHubStore
依存性
newが依存性を生む.
GitHubの保存形式がDatabaseに固定されてしまう.
GitHubDatabase
new
GitHub
<interface>
GitHubStore
class GitHub {
private GitHubStore store = new GitHubDatabase();
}
依存性
依存性はシステムの振る舞いを固定する.
GitHubDatabase
new
GitHub
<interface>
GitHubStore
class GitHub {
private GitHubStore store = new GitHubDatabase();
}
依存性
依存性はシステムの振る舞いを固定する.
依存性はシステムの多様性を殺す.
GitHubDatabase
new
GitHub
<interface>
GitHubStore
class GitHub {
private GitHubStore store = new GitHubDatabase();
}
依存性
依存性はテストの振る舞いを固定する.
依存性はテストの多様性を殺す.
テストが辛い.
DEPENDENCY INJECTION
• 制御の反転 – Inversion of Control
• ハリウッドの原則 – Hollywood Principle
依存性の注入
GitHub GitHubDatabase
new
ハリウッドの原則「おまえが呼ぶな, おれが呼ぶ」
依存性の注入
GitHub GitHubDatabase
new
依存性の注入
GitHub GitHubDatabase
Inject
ハリウッドの原則「おまえが呼ぶな, おれが呼ぶ」
class GitHub {
public GitHub(GitHubStore store) {...}
}
new GitHub(new GitHubDatabase());
依存性の注入
GitHub GitHubDatabase
Inject
ハリウッドの原則「おまえが呼ぶな, おれが呼ぶ」
class GitHub {
public GitHub(GitHubStore store) {...}
}
new GitHub(new GitHubDatabase());
依存性の注入
ハリウッドの原則「おまえが呼ぶな, おれが呼ぶ」
GitHub GitHubStore
GitHubDatabase
Client
new
Inject
class GitHub {
public GitHub(GitHubStore store) {...}
}
new GitHub(new GitHubDatabase());
依存性の注入
GitHubの依存性を制御できるようになった.
e.g. 商用ではDatabase, 検証用ではMemory cacheに...
切り替えが容易になって, テストが楽になる!
CREATION DESIGN
new, new, new...
制御の反転だけでは生成コードが散在する
new, new, new...
制御の反転だけでは生成コードが散在する
ClientA:
new GitHub(new GitHubDatabase());
ClientB:
setDatabase(new GitHubDatabase());
ClientC:
create(new GitHubDatabase());
new, new, new...
e.g. UnitTestではMemory cacheでテストしたい
ClientA:
new GitHub(new GitHubDatabase());
ClientB:
setDatabase(new GitHubDatabase());
ClientC:
create(new GitHubDatabase());
new, new, new...
e.g. UnitTestではMemory cacheでテストしたい
ClientA:
new GitHub(new GitHubDatabase() new GitHubMemcached());
ClientB:
setDatabase(new GitHubDatabase() new GitHubMemcached());
ClientC:
create(new GitHubDatabase() new GitHubMemcached());
new, new, new...
クライアントも含めたテストが難しい...
システム全体の依存性を変更するのが難儀...
ClientA:
new GitHub(new GitHubDatabase() new GitHubMemcached());
ClientB:
setDatabase(new GitHubDatabase() new GitHubMemcached());
ClientC:
create(new GitHubDatabase() new GitHubMemcached());
FACTORY PATTERN
Factory Pattern
GitHub GitHubStore
GitHubDatabase
Client
Factory Pattern適用前
Factory Pattern
GitHub GitHubStore
GitHubDatabase
Client
new
Factory Pattern適用前
Factory Pattern
GitHub GitHubStore
GitHubDatabase
Client
Inject
GitHubDatabase
Factory Pattern適用前
Factory Pattern
GitHub GitHubStore
GitHubDatabase
Client
Inject
GitHubDatabase
Factory Pattern適用前
class GitHub {
public GitHub(GitHubStore store) {...}
}
new GitHub(new GitHubDatabase());
Factory Pattern
Factory Pattern適用後
GitHub GitHubStore
GitHubDatabase
Client
Factory Pattern
生成処理をFactoryに委譲して問題領域を局所化
GitHub GitHubStore
GitHubDatabase
Client
Factory
Factory Pattern
生成処理をFactoryに委譲して問題領域を局所化
GitHub GitHubStore
GitHubDatabase
Client
Factory
Get
Factory Pattern
FactoryがGitHubDatabaseを生成する
GitHub GitHubStore
GitHubDatabase
Client
Factory
new
Factory Pattern
FactoryがGitHubDatabaseを生成する
GitHub GitHubStore
GitHubDatabase
Client
Factory
Factory Pattern
Clientを実装の詳細から隠蔽する
GitHub GitHubStore
GitHubDatabase
Client
Factory
Inject
Factory Pattern
Clientを実装の詳細から隠蔽する
GitHub GitHubStore
GitHubDatabase
Client
Factory
new GitHub(Factory.getGitHubStore(type, flag));
Inject
FACTORYの問題
Factoryの問題
Factoryの問題
• 多くの生成処理を委譲されて肥大化する
Factoryの問題
• 多くの生成処理を委譲されて肥大化する
• 生成順序, 構築方法に責任を持つ責務過多
Factoryの問題
• 多くの生成処理を委譲されて肥大化する
• 生成順序, 構築方法に責任を持つ責務過多
• Shared Object? Singleton?
Factoryの問題
• 多くの生成処理を委譲されて肥大化する
• 生成順序, 構築方法に責任を持つ責務過多
• Shared Object? Singleton?
• Lifecycle, Scopeの管理も必要
Factoryの問題
依然, 散在するコピペコード
Factoryの問題
依然, 散在するコピペコード
Factory.getGitHubStore(type, flag);
Factory.getGitHubStore(type, flag);
Factory.getGitHubStore(type, flag);
Factory.getGitHubStore(type, flag);
Factory.getGitHubStore(type, flag);
Factory.getGitHubStore(type, flag);
Factory.getGitHubStore(type, flag);
Factory.getGitHubStore(type, flag);
Factory.getGitHubStore(type, flag);
Factory.getGitHubStore(type, flag);
Factory.getGitHubStore(type, flag);
Factory.getGitHubStore(type, flag);
Factory.getGitHubStore(type, flag);
Factory.getGitHubStore(type, flag);
解決策
DI FRAMEWORK
DI Framework
DI Framework
• コピペコードの排除
DI Framework
• コピペコードの排除
• 依存オブジェクトの管理を委譲
DI Framework
• コピペコードの排除
• 依存オブジェクトの管理を委譲
GitHub GitHubStore
GitHubDatabase
Client
Factory
new
Inject
依存オブジェクト
DI Framework
• コピペコードの排除
• 依存オブジェクトの管理を委譲
GitHub GitHubStore
GitHubDatabase
Client
new
DI FW.
Inject
依存オブジェクト
DAGGER2
Dagger2
• for Java & Android
• Annotation Processingベースでデバッグが楽
• 高速
• コンパイル時に依存性の検証を行う
• Googleがメンテナ
Dagger2
DI Frameworkの3ステップ
Dagger2
DI Frameworkの3ステップ
1. 依存性の要求
Dagger2
Request Dependency
GitHub
Dagger2
DI Frameworkの3ステップ
1. 依存性の要求
2. 依存性の探索
Dagger2GitHub
Lookup Dependency
Dagger2
DI Frameworkの3ステップ
1. 依存性の要求
2. 依存性の探索
3. 依存性の充足
Dagger2
Inject Dependency
GitHub
依存性の要求
依存性の要求
依存オブジェクトをどのように要求するのか?
Request Dependency
GitHub
依存性の要求
依存オブジェクトは @Inject で要求する
Dagger2
GitHub
GitHubStore
依存性の要求
依存オブジェクトは @Inject で要求する
Dagger2
GitHub
GitHubStore
@Inject GitHubStore store;
依存性の要求
依存オブジェクトは @Inject で要求する
Dagger2
GitHub
GitHubStore
Request Dependency
@Inject GitHubStore store;
依存性の探索
依存性の探索
依存性オブジェクトをどのように探索するのか?
Dagger2
Lookup Dependency
依存性の探索
Dagger2
GitHubDatabase
依存性の探索
Dagger2
GitHubDatabase
class GitHubDatabase {
@Inject GitHubDatabase() {...}
}
依存性の探索
Dagger2
GitHubDatabase
lookup & new
class GitHubDatabase {
@Inject GitHubDatabase() {...}
}
依存性の探索
Dagger2
GitHubDatabase
GitHubDatabase
依存性の充足
依存性の充足
依存性をどのように充足するのか
Dagger2
Inject Dependency
GitHub
Dagger2GitHub
依存性の充足
ソフトウェア全体の依存性を満たす
GitHubStore GitHubDatabase
Dagger2GitHub
依存性の充足
ソフトウェア全体の依存性を満たす
GitHubStore GitHubDatabase
DI
Dagger2GitHub
依存性の充足
ソフトウェア全体の依存性を満たす
GitHubDatabase GitHubDatabase
Dagger2
依存性の充足
ソフトウェア全体の依存性を満たす
GitHub
GitHubWebApi
GitHubDatabase GitHubDatabase
依存性の充足
ソフトウェア全体の依存性を満たす
Dagger2GitHub
GitHubWebApi GitHubWebApi
GitHubDatabase GitHubDatabase
依存性の充足
ソフトウェア全体の依存性を満たす
Dagger2GitHub
GitHubWebApi
DI
GitHubDatabase
GitHubWebApi
GitHubDatabase
依存性の充足
ソフトウェア全体の依存性を満たす
Dagger2GitHub
GitHubDatabase
GitHubWebApi GitHubWebApi
GitHubDatabase
依存性の充足
ソフトウェア全体の依存性を満たす
Dagger2RepositoryViewer
GitHub
GitHubDatabase
GitHubWebApi
GitHub
GitHubWebApi
GitHubDatabase
依存性の充足
ソフトウェア全体の依存性を満たす
Dagger2RepositoryViewer
GitHub
GitHubDatabase
GitHubWebApi
GitHub
lookup & new
GitHubWebApi
GitHubDatabase
依存性の充足
ソフトウェア全体の依存性を満たす
Dagger2
GitHub
RepositoryViewer
GitHub
GitHubWebApi
GitHubDatabase
依存性の充足
ソフトウェア全体の依存性を満たす
Dagger2
DI
RepositoryViewer
GitHub GitHub
GitHubWebApi
GitHubDatabase
依存性の充足
ソフトウェア全体の依存性を満たす
Dagger2RepositoryViewer
GitHub GitHub
GitHubWebApi
GitHubDatabase
依存性の充足
ソフトウェア全体の依存性を満たす
Dagger2RepositoryViewer
GitHub GitHub
lookup & new
GitHubWebApi
GitHubDatabase
依存性の充足
ソフトウェア全体の依存性を満たす
Dagger2
RepositoryViewer
GitHub
GitHubWebApi
GitHubDatabase
依存性の充足
ソフトウェア全体の依存性を満たす
Dagger2
GitHubDatabaseGitHubWebApi
GitHub
RepositoryViewer RepositoryViewer
GitHub
GitHubWebApi
GitHubDatabase
依存性の充足
Graph
GitHubDatabaseGitHubWebApi
GitHub
RepositoryViewer
続. 依存性の探索
依存性の探索
コンストラクタへの@Injectでは解決できないケース
依存性の探索
コンストラクタへの@Injectでは解決できないケース
• インタフェースの解決(具体化)
• 管理外クラスへの@Inject宣言
• オブジェクトの構築を伴う生成
PROVIDE DEPENDENCY
依存性の提供
• Dagger2に閉じて解決できない
特殊な依存性を解決するFactory methodの提供
依存性の提供
Instance Provider
provideGitHubStore
依存性の提供
@Provides
Instance Provider
provideGitHubStore
依存性の提供
@Provides
Instance Provider
provideGitHubStore
@Provides GitHubStore provideGitHubStore() {
return new GitHubDatabase();
}
依存性の提供
Dagger2GitHub
GitHubStore
Instance Provider
provideGitHubStore
GitHubDatabase
依存性の提供
Dagger2GitHub
GitHubStore
Instance Provider
provideGitHubStore
GitHubDatabase
Request
Dependency
依存性の提供
Dagger2GitHub
GitHubStore
Instance Provider
provideGitHubStore
Resolved Dependency
GitHubDatabase
依存性の提供
Dagger2GitHub
GitHubStore
Instance Provider
provideGitHubStore
Call Factory Method
GitHubDatabase
依存性の提供
Dagger2GitHub
GitHubStore
Instance Provider
provideGitHubStore
new
GitHubDatabase
依存性の提供
Dagger2GitHub
GitHubStore
Instance Provider
provideGitHubStore
GitHubDatabase
GitHubDatabase
依存性の提供
Dagger2GitHub
GitHubStore
DI
GitHubDatabase
Instance Provider
provideGitHubStore
GitHubDatabase
依存性の提供
GitHub
GitHubDatabase
Dagger2
GitHubDatabase
Instance Provider
provideGitHubStore
GitHubDatabase
依存性の提供
@Provides
Instance Provider
provideGitHubStore
@Provides GitHubStore provideGitHubStore() {
return new GitHubDatabase();
}
依存性の提供
@Provides
• インタフェースの解決(具体化)
• 管理外クラスへの@Inject宣言
• オブジェクトの構築を伴う生成
Instance Provider
provideGitHubStore
@Provides GitHubStore provideGitHubStore() {
return new GitHubDatabase();
}
依存性の提供
@Providesを持つクラスには @Module を付与する
@Module
class ApplicationModule {
@Provides GitHubStore provideGitHubStore() {
return new GitHubDatabase();
}
}
GRAPH
Graph
Building the Graph
Graphは “Component” の単位で管理する
GitHubDatabaseGitHubWebApi
GitHub
RepositoryViewer
Dagger2
Component
Building the Graph
Graphの設計図として@Componentを宣言する
@Component(modules=ApplicationModule.class)
interface ApplicationComponent {...}
Building the Graph
Graphの操作はComponentを通じて行われる
Client Component
ApplicationComponent component
= DaggerApplicationComponent.builder()
.applicationModule(new ApplicationModule())
.build();
俯瞰図
@Inject
memberField;
@Module
@Component / Dagger2
@Provide
Interface
/ Unmanaged Class
Graph
CREATE / BUILD
PROVIDE
FACTORY METHOD
DEPENDENCY
INJECTION
@Inject
Constructor()
CREATE
依存オブジェクト
依存性の充足
依存性の要求
TEST & ARCHITECTURE
Test & Architecture
DIが促進するもの
• レイヤーをきれいに分離できる
• レイヤーが独立し, レイヤーの差し替えが容易
• テストが楽になる!
Test & Architecture
テスタビリティの向上
• テストは検証用モジュールで実施
• テスト用レイヤに差し替え etc.
DEMO
Demo
商用…WebAPIの結果を表示&Databaseへ永続化
検証用…WebAPIの結果を表示&オンメモリキャッシュ
シナリオ:
通常は商用環境で試験.
ただし, AWS Device Farmでは検証用で試験.
Demo
商用モジュールで実施
Dagger2
GitHubDatabase
GitHubWebApi
GitHub
RepositoryViewer
GitHubStore
Dagger2
Demo
商用モジュールで実施
DI
GitHubWebApi
GitHub
RepositoryViewer
GitHubStore
GitHubDatabase
Demo
商用モジュールで実施
GitHubWebApi
GitHub
RepositoryViewer
GitHubDatabase
Dagger2
GitHubDatabase
Demo
検証用モジュールで実施
Dagger2
GitHubMemcache
GitHubWebApi
GitHub
RepositoryViewer
GitHubStore
Demo
検証用モジュールで実施
Dagger2
GitHubMemcache
DI
GitHubWebApi
GitHub
RepositoryViewer
GitHubStore
Demo
Dagger2
GitHubMemcache
GitHubWebApi
GitHub
RepositoryViewer
GitHubMemcache
検証用モジュールで実施
Sample code
GitHub - Dagger2Sample
https://github.com/YukiMatsumura/Dagger2Sample
まとめ
Dependency Injection
• オブジェクトの生成は依存性を生む
• オブジェクトの生成はコスト/責務である
• おまえが呼ぶな, おれが呼ぶ
• DIがレイヤーの独立性を高める
• DIがテスタビリティを高める
以降, 補足
Graphの操作
ComponentでGraphの操作を定義
• Graphが属するScopeの宣言
• 依存性注入のポイントを外部公開
• 他Componentへの依存
Scope
依存オブジェクトのライフサイクルを指定
• Application単位のSingleton性を持たせる
• Activity単位のSingleton性を持たせる etc.
Qualifier
• 依存性の注入先に識別子を付ける
• 同じ型の依存性解決に使用される
Lazy Injection
• オブジェクト取得時に依存性を解決・注入する
• 遅延初期化
Provider Injection
• 依存性注入の都度newするnon-cached指定
Subcomponent
• ComponentAとComponentBに親子関係を持たせる
• ComponentA+ComponentBのGraphをつくる
dependencies
• ComponentAとComponentBに使用関係を持たせる
• ComponentA+ComponentBのGraphをつくる
以上
ご清聴ありがとうございました

Dependency injection