← Back to list

一個 Android 開發者所理解的依賴注入

1. 為何需要依賴注入

Jimmy 葉 in JoinX 哲煜科技 · 2022-06-13 10:20 · 70 claps · 8.8 min read
#dependency-injection #android #ppap #technical-notes
Open on Medium ↗

一個 Android 開發者所理解的依賴注入

https://memes.tw/maker/painter/5455

https://memes.tw/maker/painter/5455

1. 為何需要依賴注入

既然要解讀,那首先必須要先大概說明一下何謂依賴注入,以及為何需要, 我們直接來看例子吧!

!!!注意事項!!! 接下來的舉例會透露筆者年紀,若覺得年代久遠還請多包涵

不知道大家還記不記得,在 2016 年時,有一位身穿豹紋裝,留著一搓小鬍子的表演者出現在了大眾的視野,那就是~~~~~

Piko太郎!

https://pt.wikipedia.org/wiki/Ficheiro:PPAP_%28Pen-Pineapple-Apple-Pen%29.png

https://pt.wikipedia.org/wiki/Ficheiro:PPAP_%28Pen-Pineapple-Apple-Pen%29.png

他的代表作品 PPAP 在當時用洗腦的旋律及誇張的表演深植人心,這首歌曲在YouTube目前已累積了4億次的驚人觀看次數。

[embed]

確實是很厲害沒錯,但和今天的主題似乎沒什麼關係,那我們趕快轉換成程式碼的形式。

今天 Piko 太郎為了表演,要我們準備了兩樣物品,讓他可以在歌詞中帶入,分別是:

class Apple() {
    fun getName(): String {
        return "apple"
    }
}
class Pen() {
    fun getName(): String {
        return "pen"
    }
}

並且會由 PPAPLyrics 合併成 PPAP 的歌詞

class PPAPLyrics () {

    val pen = Pen()

    val apple = Apple()

    fun combined() {
        print(
        apple.getName() 
        + 
        pen.getName())
    }
}

立刻請 Piko 太郎開始表演吧!:

fun main() {
    val firstPPAPLyrics = PPAPLyrics()

    firstPPAPLyrics.combined()
}

https://memes.tw/maker/painter/42370

https://memes.tw/maker/painter/42370

水啦~看起來很不錯喔,完整組成歌詞了。

接著請 Piko 太郎繼續唱下去:

fun main() {
    val firstPPAPLyrics = PPAPLyrics()
    val secondPPAPLyrics = PPAPLyrics()
    firstPPAPLyrics.combined()
    secondPPAPLyrics.combined()
}

https://memes.tw/maker/painter/42370

https://memes.tw/maker/painter/42370

疑,怎麼唱出來的歌詞都是 apple pen ,這樣聽眾會傻眼貓咪吧 ~ 不行不行,得給快更換物品。

但真的要換歌詞內的物品時,發現根本換不了,為什麼?

因為歌詞的形式被限制死了。

PPAP 的歌詞 combined 時就是只能接受 apple & pen,這就是高度耦合。要解決這樣的情況,就必須把歌詞生成的對象從 applepen 的限制中解放,我們轉而替生成歌詞的「行為」建立一個基底,那就是:

interface LyricsName {
    //物品名稱
    fun name(): String

    //複數形容詞
    fun doubleAdjective():String
}

並且把 PPAPLyrics 的建立行為改成:

class PPAPLyrics(val aPartLyricsName: LyricsName, val bPartLyricsName: LyricsName) {

    fun combined() {

        if (aPartLyricsName.name() == bPartLyricsName.name()) {
            //如果物品名稱一樣就加複數
            println("${aPartLyricsName.doubleAdjective()}
                     +    
                     ${aPartLyricsName.name()}")
        } else {
            //如果不一樣就拼起來
            println(aPartLyricsName.name()
                    +
                    bPartLyricsName.name())
        }
    }
}

實際來修改看看

fun main() {
    val firstPPAPLyrics = PPAPLyrics(Apple(), Pen())
    val secondPPAPLyrics = PPAPLyrics(Pineapple(), Pen())
    val thirdPPAPLyrics = PPAPLyrics(Pen(), Pen())

    firstPPAPLyrics.combined()
    secondPPAPLyrics.combined()
    thirdPPAPLyrics.combined()
}
class Apple : LyricsName {
    override fun name(): String {
        return "apple"
    }

    override fun doubleAdjective(): String {
        return "big"
    }
}

class Pen : LyricsName {
    override fun name(): String {
        return "pen"
    }

    override fun doubleAdjective(): String {
        return "long"
    }
}

class Pineapple : LyricsName {
    override fun name(): String {
        return "pineapple"
    }

    override fun doubleAdjective(): String {
        return "big"
    }
}

我們再請 Piko 太郎確認一下結果:

https://memes.tw/maker/painter/42370

https://memes.tw/maker/painter/42370

看起來好像給什麼物品,Piko 太郎都能唱出對應的歌詞了,仔細分析一下我們可以發現,LyricsName PPAPLyrics 彼此的職責是分開的:

  • 實作 LyricsName 的物件負責生成歌詞,不會知道拼湊的順序
  • PPAPLyrics 負責歌詞的合併順序,不會知道歌詞是什麼

由於每個角色在整個流程只專注在自己負責的行為,讓流程擁有了高複用性及維護性。

因此我們似乎可以這樣解釋:

解耦就是「異中求同,同中尋異的脈絡梳理」。

目前看下來,解耦的道理好像很單純,但實做起來卻有一定難度,因為現實需求中遇到的情境更為複雜,且沒有一定的經驗,開發者在當下不容易分辨出在整個流程中什麼是「可重複的」,什麽是「獨立的」,這些都需要時間來熟悉。

但能不能夠有技巧的熟悉解耦呢?

有一個關鍵,也是我們今天的主題,LyricsName是透過建構子的方式「注入」進 PPAPLyrics 中的,所以才能夠成功解耦,我們稱這個形式為「依賴注入」,而透過 interface 進行將低層次 LyricsName 物件注入進高層次 PPAPLyrics 物件的流程稱為「依賴反轉」。

2. Android 中依賴注入的種類

Android 官方說明中有解釋,注入形式大概可以分成兩種

# Constructor Injection (建構子注入)

應該是最方便使用的注入形式,使用建構子注入相比 setter 注入有個很大的好處,就像上面所舉的例子,要使用 PPAPLyrics 時,開發者就會知道一定要傳入兩個 LyricsName 後整個邏輯才能被使用,因此都會優先使用建構子注入的方式。

Android 當中官方相對的範例?

我們常用的 Recyclerview.ViewHolder 就是這樣的狀況,ViewHoder 顧名思義就是 View 的掌控者,之後的資料綁定就是由 ViewHolder 實現,所以他在建立的當下就必須要傳入繼承 View 類別的建構子, Recyclerview 只處理到建立 ViewHolder 這個「共同行為」,接下來的實作細節就給 ViewHolder 做統一或個別處理。

class ExampleViewHolder(var dataBinding: ItemExampleinding) :
    RecyclerView.ViewHolder(dataBinding.root)

# Field Injection or Setter Injection (setter注入)

官方給出的最常見注入方式,其原因如同官方給出的解釋:「由系統實例化,因此無法進行建構子注入」,只好退而求其次的注入方式。

Android 當中官方相對的範例?

其實這樣的範例在 Android 中很常見,開發中一定會看到的像「生命週期」的時機,甚至是 onClickListener 都是這樣的注入方式,生命週期只負責告知開發者整個聲明週期的週期流程,具體後續行為由開發者定義,onClickListener 只負責傳遞點擊事件,事件後續行為也是由開發者定義。

3.結論

本篇文章稍微講解了為何需要依賴注入原因的及部分官方的範例,提及到了物件導向程式設計基本原則 — SOLID 中的

  1. S: Single responsibility principle(SRP) 單一職責

一個類別只負責一件事情。

  1. D: Dependency Inversion Principle(DIP) 依賴反轉

程式不能寫的太死,盡量先拉出概念性的東西

但實際上在維護時,可能會遇到更多有關注入的障礙,因此下篇文章我們會繼續帶到官方給出的依賴注入 Library,有興趣的話也歡迎持續關注~

4.參考來源:

Android 中的依赖项注入

菜雞新訓記 (6): 使用 依賴注入 (Dependency Injection) 來解除強耦合吧

Android中的依赖注入(概念篇)

物件導向程式設計基本原則 — SOLID


메타데이터
post_id
176c2a4ce2df
slug
面向-android-開發者的依賴注入解讀-176c2a4ce2df
url
https://medium.com/twjoin/%E9%9D%A2%E5%90%91-android-%E9%96%8B%E7%99%BC%E8%80%85%E7%9A%84%E4%BE%9D%E8%B3%B4%E6%B3%A8%E5%85%A5%E8%A7%A3%E8%AE%80-176c2a4ce2df
canonical_url
https://medium.com/twjoin/%E9%9D%A2%E5%90%91-android-%E9%96%8B%E7%99%BC%E8%80%85%E7%9A%84%E4%BE%9D%E8%B3%B4%E6%B3%A8%E5%85%A5%E8%A7%A3%E8%AE%80-176c2a4ce2df
author_url
https://medium.com/@linlin3253job
status
ok
fetched_at
2026-06-20 20:29:01