# Dependency Injection in Flutter 💉
---
1. 什麼是依賴注入,及依賴注入可以解決的問題
2. IoC & DI 區別
3. 使用依賴注入會遇到什麼問題
---
``` dart
class MusicRecommender {
MusicRepository repo;
MusicRecommender() {
repo = MusicRepository();
}
List<Music> recommendMusic(User user) {
return repo.getMusics()
.where((music) => user.taste.contains(music.genre))
.toList();
}
}
class MusicRepository {
List<Music> getMusics() => // get musics from Firebase
}
```
Note:
* 不可能通過模擬 **MusicRepository** 來測試 **MusicRecommender** 類
* 如果你有其他的 MusicRepository (ex: 上面是來自 Firebase,可能公司決定替換為 Spotify
---
``` dart
abstract class MusicRepository {
List<Music> getMusics();
}
class FirebaseMusicRepository implement MusicRepository {
List<Music> getMusics() => // get music from Firebase
}
class MySqlMusicRepository implement MusicRepository {
List<Music> getMusics() => // get music from MySql
}
class MockMusicRepository implement MusicRepository {
List<Music> getMusics() => // get music from predefined variable
}
```
Note:
* 你和你的同事事先定義好MusicRepository介面,兩邊就可以同步進行。
* 你的MusicRecommender依賴MusicRepository這個介面,單元測試時就可以提供MockMusicRepository。如果這時候單元測試沒過,那肯定就是你的問題。
* 從類別的相依性來看,透過定義一個介面,使你的類別A不再依賴另一個類別B,而是兩個類別都依賴介面,進而降低了彼此的耦合程度,此一過程正是S.O.L.I.D中的依賴反轉原則(Dependency Inversion Principle, DIP)。
* 而從程式執行的流程來看,類別A不主動去尋找自己需要的類別B,而是被動的由外界輸入,則可以說是一種控制反轉原則(Inversion of Control, IoC)的體現
---
``` dart
class MusicRecommender {
final MusicRepository repo;
MusicRecommender(this.repo); // This is DI
List<Music> recommendMusic(User user) {
return repo.getMusics()
.where((music) => user.taste.contains(music.genre))
.toList();
}
}
abstract class MusicRepository {
List<Music> getMusics();
}
class FirebaseMusicRepository implement MusicRepository {
List<Music> getMusics() => // get music from Firebase
}
```
Note: IoC 是一個通用術語,意思不是讓應用程序調用框架中的方法,而是框架調用應用程序提供的實現。
DI 是 IoC 的一種形式,其中實現通過構造函數/設置器/服務查找傳遞到對像中,對象將“依賴”它們以正確運行。
---
``` dart
class MusicRecommender {
MusicRepository repo;
void setRepo(MusicRepository repo) => this.repo = repo;
}
```
Note:
* 上面的例子是從constructor傳入,但我們也可以透過一個setter傳入
* 雖然我們可能會在某些地方不自覺地使用了被稱作DI的模式,但是當我們試著盡可能在所有地方都套用DI時,問題就來了。
---
``` dart
class ViewModel {
final firebaseMusicRepository = FirebaseMusicRepository();
final musicRecommender = MusicRecommender(firebaseMusicRepository);
}
class MusicRecommender {
final MusicRepository repo;
MusicRecommender(this.repo); // This is DI
}
abstract class MusicRepository {
List<Music> getMusics();
}
class FirebaseMusicRepository implement MusicRepository {
List<Music> getMusics() => // get music from Firebase
}
```
Note:
* 好我不能在類別A內實例化類別B,那我要在哪裡實例化?類別A上層的類別C嗎?
* 這樣反而更奇怪,C原本只依賴A,現在變得也依賴不相干的B了,更何況不論是A還是B,都不該在C裡面實例化不是嗎?那難道要在上一層的D...
---
```
void main() {
final firebaseMusicRepository = FirebaseMusicRepository();
final musicRecommender = MusicRecommender(firebaseMusicRepository);
C c = C();
D d = D(c, b);
E e = E(d, a)
}
```
Note:
* 畢竟我們是物件導向,幾乎所有東西都是物件,當我們把所有物件實例化的程式碼從類別定義中全部拉出來,不斷往上提昇,最後的終點就是...main()
* 把這些建造函式全部移到某個專職生產所有物件的類別,然後再加上一些cache的機制(畢竟你不一定想每次都重建這些物件),我們就得到了一個自己手工建立的IoC Container。我們可以把這個IoC Container設成全域變數,那麼當程式中其它並非由自己建立的物件(Widget)須要任何物件時,就可以輕易的取得
---
``` dart
class IocContainer {
A buildA() => A();
B buildB() => B(buildA());
C buildC() => C();
D buildD() => D(buildC(), buildB());
E buildE() => E(buildD(), buildA());
}
```
Note: 當然其實不一定要是main,任何一個程式啟動時,足夠早的時間點都可以。你可能會說「在啟動時實例化所有物件!?這樣啟動會很慢吧?」沒錯,所以我們其實只是宣告了A, B, C, D, E的建造函式
---

---
``` dart
void main() {
final container = IocContainer();
container.map<A>((container) => AImpl());
container.map<B>((container) => B(container.get<A>())); // do this;
container.map<B>((container) => B(AImpl())); // not this;
container.map<C>((container) => C(), cache: true);
....
}
void somewhereElse() {
B b = container.get<B>(); // recreated
C c = container.get<C>() // get from cache
}
```
Note: 嘿,當我須要一個Type B的物件時,請幫我建立一個B()。B須要一個Type A的物件,你知道該怎麼做了吧?
* 這樣的type mapping,或是說type register,和基本的cache機制,就是DI套件能幫你實作的最簡單的功能之一。例如flutter_simple_dependency_injection基本上功能就只有這樣,
---
* Singleton: Holds a reference to an object and will return it every time it’s requested.
* Lazy singleton: Uses a factory method to instantiate the object the first time it’s asked for it and will return the same thereafter.
* Factory: Uses a factory to create new instances every time it’s invoked.
---
# 從 Flutter Community 學到的 DI,可能跟你想的不一樣
---
#### 參考資料
* [Dependency Injection in Flutter- Marc Guilera](https://medium.com/flutter-community/dependency-injection-in-flutter-625650195a98)
* [days[9] = "為什麼需要依賴注入?(下)"](https://ithelp.ithome.com.tw/articles/10235302)
---
# Thanks 🥹
---
{"metaMigratedAt":"2023-06-18T03:48:45.652Z","metaMigratedFrom":"YAML","title":"Dependency Injection in Flutter","breaks":true,"slideOptions":"{\"spotlight\":{\"enabled\":false}}","contributors":"[{\"id\":\"911de02f-c8f8-4ddb-a91f-64cf7623d126\",\"add\":5006,\"del\":111}]"}