# 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的建造函式 --- ![](https://hackmd.io/_uploads/rkhF0Rw4h.png) --- ``` 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}]"}
    306 views