iT邦幫忙

第 12 屆 iThome 鐵人賽

DAY 9
1
Mobile Development

Why Flutter why? 從表層到底層,從如何到為何。系列 第 9

days[8] = "為什麼需要依賴注入?(上)"

總之,依賴注入就是為了使程式更有彈性,把class A需要的class B從外面傳入。好的講完了,可以收工了。什麼?還不到300字?好吧,繼續來混一些字數...


雖然沒有狀態管理這麼火熱,但依賴注入也是在Flutter社群經常被提到的議題,也經常有新套件出現。這有一部份是因為,Flutter界現在還沒有一個功能完善、穩定維護、被廣泛使用的依賴注入框架出現。另一部份則是因為,依賴注入這個詞,在Flutter社群有時候被挪用到了某些不相關的場景中。依賴注入到底是什麼?要解決什麼問題?為什麼我們須要它?這次就讓我們來一探究竟。

首先來看看這個簡單的例子:

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
}

我們有個音樂APP(可能是下一個Spotify!),裡面有個推薦音樂的功能。因為我們熱愛OOP,自然把它包成了MusicRecommender。接著,既然我們要推薦音樂,我們當然要有個音樂庫MusicRepository,才能取得音樂來推薦。也就是說我們的MusicRecommender依賴MusicRepository才能運作。看起來挺好的,這能出什麼問題?

想像一下以下三個情境:

  1. 你覺得你這個MusicRecommender寫得實在太好了(可能你用了深度學習+區塊鍊+量子運算blahblahblah),你決定把它開源出去讓其它做音樂APP的開發者使用。有一天你收到一個issue:「首先感謝作者做了這個超級強大的音樂推薦套件。可是瑞凡,我不想用Firebase。你能不能把它寫得更有彈性一點?
  2. 因為你們決心要做下一個Spotify,你們有一整個Flutter團隊在開發這個APP。你負責開發的其實只有MusicRecommender,而MusicRepository是由你同事負責的。你們兩個同步進行開發,但你卻不知道他最後交給你的程式碼會使用什麼資料庫,會宣告什麼函數,又有什麼參數和回傳值?你心裡想:「不是啊這樣我要怎麼寫我的MusicRecommender?」
  3. 你們的APP開發非常順利,現在MusicRecommender和MusicRepository都發展成有著一定複雜程度的class。有一天這個推薦音樂的功能出了大包,開始不斷推薦含有種族歧視的音樂。你和你同事開始互踢皮球,爭論這一定是對方的程式出了問題,而你們的資深工程師搖搖頭說:「如果你們當初有做好單元測試就不會發生這種事了...」。而你雖然默默點著頭,心裡卻想:「啊我的code就依賴他的code,要測試也只能一起測啊?測試沒過還不是不知道到底是誰的問題?」

聰明的你看著這三個情境,心裡是不是已經在吶喊著答案了呢?「介面!介面!拜託你使用介面!」
沒錯,如果今天我們的MusicRepository是個介面:

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
}
  1. 你的MusicRecommender開源函式庫的使用者可以根據介面,實作自己想要使用的資料庫。
  2. 你和你的同事事先定義好MusicRepository介面,兩邊就可以同步進行。
  3. 你的MusicRecommender依賴MusicRepository這個介面,單元測試時就可以提供MockMusicRepository。如果這時候單元測試沒過,那肯定就是你的問題

從類別的相依性來看,透過定義一個介面,使你的類別A不再依賴另一個類別B,而是兩個類別都依賴介面,進而降低了彼此的耦合程度,此一過程正是S.O.L.I.D中的依賴反轉原則(Dependency Inversion Principle, DIP)。

而從程式執行的流程來看,類別A不主動去尋找自己需要的類別B,而是被動的由外界輸入,則可以說是一種控制反轉原則(Inversion of Control, IoC)的體現。

這裡的「控制」反轉可能會比「依賴」反轉更難理解一點,到底什麼控制什麼?又是怎麼個反轉法?沒關係你並不孤單,事實上就連Martin Fowler大神也曾經有這個疑問(這篇文章很長,但強烈建議能看的人就花點時間慢慢把它看完,相信會比聽我在這裡鬼扯更有幫助)。這也沒辦法,因為IoC一開始指的並不是這件事...

很久很久以前,程式都是在命令列上執行的,而我們的音樂推薦功能可能長這樣:

>who are you?
:Joshua
>What music do you like?
:classical music
>What music do you really like?
:anime songs
>Ok here are songs that you might like...

整個流程是由你來控制的,由你決定什麼時候問什麼問題、呼叫什麼函數、要求什麼輸入,給出什麼結果。

然而,自從有了GUI以後,我們發現我們可以一次問好幾個問題,而且可以隨時改變我們的結果:
https://ithelp.ithome.com.tw/upload/images/20200909/201290537cetBtYc2L.png
這時候程式的流程變成主要是由框架事件迴圈(event loop)來決定的。我們負責去實作框架提供的介面或抽象類別(Widget),告訴框架我們想顯示什麼(build函數),怎麼跟使用者互動(onPressed函數),由框架決定什麼時候去呼叫你的程式碼。

這樣講應該更符合控制反轉這幾個字了吧?這也就是為什麼有時候IoC也被戲稱為好萊塢原則(Don't call us, we call you.)。

總之IoC這個詞的源頭來自於框架開始被大量使用,而程式的控制權從開發者本身轉移到框架身上的時代。至於為什麼十五年前開始,有許多Dependency Injection或Service Locator套件的作者把它們的套件叫做"IoC" Container,就比較難以考究了。

這裡提這個小故事主要是為了接下來終於要進入的依賴注入部份,因為在十五年後的今天,依賴注入這個詞,在Flutter社群同樣被誤用(挪用?演化?端看你對這件事的態度多負面)到了其它莫名其妙的場景上。

欲知詳情,請待下回分曉...(沒錯這整篇都是前言,到現在還沒進入DI的部份,但是相信我這一切都很有關係)


上一篇
days[7] = "三顆渲染樹是如何運作的?(四)"
下一篇
days[9] = "為什麼需要依賴注入?(下)"
系列文
Why Flutter why? 從表層到底層,從如何到為何。30
圖片
  直播研討會
圖片
{{ item.channelVendor }} {{ item.webinarstarted }} |
{{ formatDate(item.duration) }}
直播中

尚未有邦友留言

立即登入留言