當定義Dagger2 Scope時,你在定義什麼?
前言
我在簡書上寫得第一篇文章是關於Dagger2在Android平臺上新的使用方式的,見 ofollow,noindex">Dagger2在Android平臺上的新姿勢 。當時覺得真香。

真香
之後寫了一篇介紹了這種新的使用方式背後是如何實現的,見 Dagger2在Android平臺上的新魔法 。也是這篇文章讓我重新思考dagger.android是否真的給開發帶了便捷,從那之後我便放棄使用dagger.android。真諷刺,因為不瞭解覺得“真香”,因為了解而放棄。
這裡面的核心問題有兩個:
- 什麼是Scope,當你定義Scope時,你在定義什麼?
- 你是否真的需要SubComponent/Dependent Component?
1. 什麼是Component?
要想解釋什麼是Scope,必須先明白什麼是Component。Component說白了其實就是一個物件圖(object graph),它包含了很多物件,以及物件之間的依賴關係,所以我們才能通過Component直接注入我們需要的物件,而不需要考慮依賴關係的問題。

Component就是物件圖
如上圖所示,Component的物件圖包含有5個物件:A、B、C、D、E。這些物件之間有依賴關係,通過Component可以直接注入(也可以理解為獲取)這5個物件中的任意一個/幾個,不需要考慮這些物件之間的依賴關係,因為Dagger已經幫我們構建出了這麼一個物件圖。例如,需要注入A物件,需要先構建C和D兩個物件,我們不需要關心這些,直接注入A物件即可。
Component是一個物件圖,然而,Component本身也是一個物件(“面向物件”中那個物件),不同Component之間也有關聯,類比“面向物件”中的說法,Component也有“組合”和“繼承”。
1.1 Component間的依賴關係
所謂Component的“組合”是指Component之間的“依賴”關係,對應於Dagger中的Component dependencies。一個Component可以依賴另外一個/幾個Component,前面已經說過,所謂Component也就是物件圖而已,一個Component依賴另外的Component,換一種說法也就是,這個Component除了自己的物件圖外還包含有別的Component的“部分”物件圖。這裡的“部分”指的是那些已經在別的Component中被宣告/暴露出來的物件。

Component間的依賴
如上圖所示,右側的Component依賴於左側的Component,也就是說右側Component的物件圖不止包含有物件D、E,還包含有左側Component宣告/暴露出來的C物件,所以D物件可以依賴於C物件,但是不能依賴於A或者B物件。
1.2 Component間的父子關係
所謂Component的“繼承”是指Component之間的“父子”關係,對應於Dagger中的SubComponent。不同於上面提到的Component間的依賴關係,SubComponent繼承了父Component中的所有物件圖,不需要父Component進行宣告/暴露。

Component間的繼承
如上圖所示,右側SubComponent的物件圖包含了左側父Component的所有物件,不管這些物件是否在父Component中被宣告/暴露,所以物件D可以依賴於物件C和物件B。需要注意的是,左側父Component也可以是SubComponent,所以說,右側的SubComponent可能不僅包含了父Component的物件圖,還包含了爺Component的物件圖,等等。但是這並不重要,還是可以簡單的說SubComponent繼承了父Component的物件圖,至於父Component的物件圖是完全自己定義的,還是繼承自別的什麼Component,這對於當前的SubComponent而言並不重要。
2. 什麼是Scope?
上面介紹了什麼是Component,這和Scope有什麼關係呢?答案很簡單,Scope就是Component的名字而已。
眾所周知, @Singleton
也是一個Scope(如果你還不知道,就假裝自己已經知道了)。這就奇怪了,既然 @Singleton
也是一個Scope,這和我們自己定義的Scope有什麼區別,為什麼使用 @Singleton
就可以實現全域性單例,而我們自定義一個 @ActivityScope
或者 @PerActivity
就只能實現一個在每個Activity中的單例,同理也適用於 @FragmentScope
或者 @PerFragment
?這是Dagger2最微妙的部分,也是讓很多初學者困惑的地方,我當初學習Dagger2的時候就有這樣的困惑。先直接丟擲答案,其實,Dagger2沒有這樣的魔法,自定義的 @ActivityScope
, @FragmentScope
和 @Singleton
沒有什麼區別,單例的作用域取決於 Component
自身的存活範圍。
2.1 Scope就是Component的名字
前面介紹了Component之間有依賴關係和父子關係,建立這些關係的前提是每個Component都得有個“名字”。而這個名字就是Scope。
沒有“名字”(不定義Scope)的Component之間也可以建立這兩種關聯,但是實際使用當中沒有太大作用,所以你可以簡單地認為Component必須定義自己的Scope。
那麼為什麼必須給Component起個名字呢?最核心的原因在於要給單例提供作用域。依賴注入繞不開問題就是如何提供單例物件,以及這個單例物件的使用範圍(作用域)。Dagger2解決這兩個問題的關鍵就在於Scope。
之前介紹Component的物件圖時都忽略了單例的情況,下面仔細看看Component中的單例是如何存在的。

Singleton
如上圖所示,D物件是個單例,而C物件不是,每次需要C物件是都會新建一個,而D物件在該Component中始終只有一個,這是因為,單例物件D被儲存在了該Component中,只要能獲得該Component,就能獲得單例物件D。
並且,Dagger還規定了,Component中使用的Module只能提供unscope的物件(普通物件,每次需要時都新建),或者跟該Component Scope一致的物件。例如,上圖中,Component定義的Scope是 @Singleton
,那麼所有Module就只能提供unscope的物件C,或者 @Singleton
的物件D。之所以這麼規定,是因為每個Component定義了自己的Scope,並管理著自己的物件圖,與之關聯的Module提供這個Scope內的單例或者unscope就可以了,不能通過這個Component向別的Component的作用域提供單例,因為那麼別的Component的事情,與你無關。
為什麼我說Scope就是Component的名字而已?因為這個Component的Scope可以是任意Scope,可以是預設的 @Singleton
,也可以是自定義的 @ActivityScope
,或者任意別的自定義Scope都可以。這個Scope並不決定單例的範圍,它只是一個標識,並且強制要求該Component使用到的Module都遵守這個標識,不能越界。通過這個標識標註的單例都被儲存到了該Component中,只要能獲得該Component,你就能獲得這些單例,單例的範圍/作用域的關鍵在於該Component的存活範圍。在Application中儲存了該Component,那麼就是全域性單例的;在Activity中儲存了該Component,那麼就是在Activity範圍內的單例,等等。跟定義的Scope叫什麼沒有一毛錢關係。
雖說Scope的名字可以任意取,但是也不能瞎取。在程式中起名字最重要的就是表意。所以我們經常去 @ActivityScope
, @FragmentScope
這樣的名字,但是單例的作用域跟這些名字之間並沒有任何關係,並不是因為你定義的Scope叫 @ActivityScope
,所以單例的作用域就被限定在了Activity的範圍內。就好像,你養了一隻狗,你非得給他取名叫“喵咪”,也不能說你錯,但是大家肯定覺得你有毛病。反過來,即使他叫“喵咪”,他也仍然是一隻狗。
3. 是否需要SubComponent/Dependent Component
假設你平時做Android開發時會為每個Activity和Fragment定義相應的Component,或者像我之前那樣為使用dagger.android。當你明白了什麼是Component,什麼是Scope之後,你有沒有想過這麼一個問題,這些定義的Component是否有必要。
如前所述,定義Scope就是給Component起名字,而給Component起名字主要是為了構建多個Component之間的關聯。試問,構建這些關聯的目的是什麼?當然是為了分層管理,在不同作用域內共享一些單例。如果你定義了一個SubComponent/Dependent Component,與之關聯的所有Module卻並沒有向它提供單例(所有Module全部提供的是unscope物件),那麼這個SubComponent/Dependent Component是沒必要的,你完全可以使用上一層的Component(父Component/被依賴的Component)去完成相同的任務,除非你確實需要為這個Component提供這些Module,以對物件進行分層管理。甚至,更極端的情況,這個SubComponent/Dependent Component完全不需要Module,在這種情況下,使用這個SubComponent/Dependent Component完全是多餘的。

多餘的Component
而我發現自己之前使用dagger.android時就是這樣的,完全沒有為SubComponent提供新的Module,因此我現在已經不再使用dagger.andoird。
在我看來,在Android平臺上很多SubComponent/Dependent Component是沒有必要的,多數情況下,我們需要的單例是全域性的,把這個全域性的Component儲存在Application中即可。並不經常需要在Activity、Fragment範圍內共享什麼單例。如果你使用MVP模式,那麼有時確實需要在Activity範圍內去共享某個Presenter,但是也沒必要為每個Activity、Fragment建立Component。這種誤用往往是因為不瞭解Dagger,把這種使用方式當成一種正規化,彷彿只有這樣Dagger才能正常執行,其實完全不是這樣的。
4. 總結
Component就是個物件圖,Scope是給Component起得名字,目的是為了構建Component之間的依賴、繼承關係,這些關係體現了Dagger對於依賴注入的分層管理。Module只能提供unscope或者和Component相同Scope的物件,這是因為各個Component管理各自的物件圖,別的Component無法插手。單例物件被儲存在對應的Component中,這個Component的存活範圍就是單例的作用域,跟Scope叫什麼名字沒有關係。當你新建SubComponent/Dependent Component時,多問問自己有沒有必要。