Effective Java 3rd 條目5 依賴注射優(yōu)于硬耦合資源

很多類依賴于一個(gè)或者多個(gè)相關(guān)資源。比如,一個(gè)拼寫檢測(cè)器依賴一個(gè)字典。這樣的類實(shí)現(xiàn)為靜態(tài)效用類,這是很常見(jiàn)的(條目4):

// 不恰當(dāng)?shù)撵o態(tài)效用的使用 - 不靈活而且不可測(cè)試!
public class SpellChecker { 
    private static final Lexicon dictionary = ...;
    
    private SpellChecker() {} // 不可實(shí)例化化

    public static boolean isValid(String word) { ... } 
    public static List<String> suggestions(String typo) { ... }
}

相似地,這些類實(shí)現(xiàn)為單例,這也是很常見(jiàn)的(條目3):

// 不恰當(dāng)?shù)膯卫氖褂?- 不靈活而且不可測(cè)試! 
public class SpellChecker { 
    private final Lexicon dictionary = ...;
    
    private SpellChecker(...) {} 
    public static INSTANCE = new SpellChecker(...);
    
    public boolean isValid(String word) { ... } 
    public List<String> suggestions(String typo) { ... }
}

這些方法沒(méi)有一個(gè)是令人滿意的,因?yàn)樗麄兗僭O(shè)只有一個(gè)值得使用的字典。在實(shí)踐中,每個(gè)語(yǔ)言有自己的字典,特定的字典使用特定的詞匯。而且,測(cè)試可能有特定的字典。認(rèn)為單個(gè)字典對(duì)所有時(shí)間都?jí)蛴?,這是一廂情愿的想法。

你可能用這種方法讓SpellChecker支持多個(gè)字典:字典的域?yàn)榉莊inal,然后在目前的拼寫檢測(cè)器中添加一個(gè)改變字典的方法。但是這個(gè)可能很笨拙,容易出錯(cuò),而且在多線程環(huán)境不能工作。靜態(tài)效用類和單例對(duì)于這樣的類是不恰當(dāng)?shù)模核男袨槭怯上嚓P(guān)資源參數(shù)化。

要求的是這種能力:支持這個(gè)類的多個(gè)實(shí)例(在我們的例子中,SpellChecker),每個(gè)實(shí)例使用客戶端要求的資源(我們的例子中,字典)。滿足需求的一個(gè)簡(jiǎn)單模式是,當(dāng)創(chuàng)建一個(gè)新實(shí)例時(shí),把資源傳入到構(gòu)造子。這是依賴注入的一個(gè)形式:字典作為拼寫檢測(cè)器的一個(gè)依賴,當(dāng)創(chuàng)建時(shí)它被注入到拼寫檢測(cè)器中。

// 依賴注入具有靈活性和可測(cè)試性 
public class SpellChecker { 
    private final Lexicon dictionary;

    public SpellChecker(Lexicon dictionary) { 
        this.dictionary = Objects.requireNonNull(dictionary); 
    }

    public boolean isValid(String word) { ... } 
    public List<String> suggestions(String typo) { ... }
}

依賴注射是如此簡(jiǎn)單,以致許多程序員多年使用而不知道它的名字。雖然我們的拼寫檢測(cè)器例子僅僅只有單一資源(即,字典),依賴注射對(duì)于任意資源數(shù)量和任意依賴圖譜也起作用。它保持不可變性(條目17),所以多個(gè)客戶端可以分享依賴對(duì)象(假設(shè)客戶端需要同樣的相關(guān)資源)。依賴注射同樣可以應(yīng)用到構(gòu)造子,靜態(tài)工廠(條目1)和builder(條目2)。

這個(gè)模式的一個(gè)有用變體是,把資源工廠傳遞給構(gòu)造子。工廠是一個(gè)對(duì)象,可以重復(fù)調(diào)用創(chuàng)建一種類型的實(shí)例。這樣的工廠具體表現(xiàn)為工廠方法模式(Factory Method)[Gamma95]。Java 8引入的Supplier<T>接口完美代表工廠。輸入有Supplier<T>的方法,往往用受限的通配類型(bounded wildcard type)(條目31)來(lái)限制工廠類型參數(shù),這讓客戶端傳入一個(gè)工廠,這個(gè)工廠可以創(chuàng)建指定類型的任何子類型。比如,這里有個(gè)方法,用客戶端提供的工廠生成每個(gè)地磚來(lái)鋪設(shè)鑲嵌地磚。

Mosaic create(Supplier<? extends Tile> tileFactory) { ... }

盡管依賴注射極大提高了靈活性和可測(cè)試性,但是它使得大項(xiàng)目凌亂,大項(xiàng)目往往包含成千個(gè)依賴。用依賴注射框架(∫dependency injection framework)可以消除凌亂,比如Dagger[Dagger]、Guice[Guice]或者Spring[Spring]。使用這些框架不在這本書的范圍,但是記住,為手動(dòng)依賴注入設(shè)計(jì)的API,可以用這些框架輕松適配。

總之,不要用單例或者靜態(tài)效用類實(shí)現(xiàn)這樣的類,這個(gè)類依賴一個(gè)或者多個(gè)相關(guān)資源,而這些資源的行為影響了這個(gè)類。不要用這個(gè)類直接創(chuàng)建這些資源。相反,傳遞資源或者工廠到構(gòu)造子來(lái)創(chuàng)建它們(或者靜態(tài)工廠或者builder)。這個(gè)實(shí)踐,叫做依賴注射,大大的增強(qiáng)了一個(gè)類的靈活性、重用性和可測(cè)試性。

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請(qǐng)結(jié)合常識(shí)與多方信息審慎甄別。
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡(jiǎn)書系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

  • Spring Cloud為開發(fā)人員提供了快速構(gòu)建分布式系統(tǒng)中一些常見(jiàn)模式的工具(例如配置管理,服務(wù)發(fā)現(xiàn),斷路器,智...
    卡卡羅2017閱讀 136,634評(píng)論 19 139
  • Android 自定義View的各種姿勢(shì)1 Activity的顯示之ViewRootImpl詳解 Activity...
    passiontim閱讀 179,167評(píng)論 25 708
  • 目錄 第二章 創(chuàng)建和銷毀對(duì)象 1 考慮用靜態(tài)工廠方法替代構(gòu)造器 對(duì)于代碼來(lái)說(shuō), 清晰和簡(jiǎn)潔是最重要的. 代碼應(yīng)該被...
    高廣超閱讀 1,525評(píng)論 0 12
  • 導(dǎo)語(yǔ):如何充分的利用自媒體平臺(tái)的推廣-如果你只會(huì)用微信公眾號(hào)做推廣,那么你太OUT了。 流量的時(shí)代逐漸轉(zhuǎn)為內(nèi)容時(shí)代...
    楊小龍說(shuō)閱讀 2,437評(píng)論 0 14
  • 矯情的話懶得說(shuō),因?yàn)樾腋R炎灾??
    C小蕓閱讀 131評(píng)論 0 0

友情鏈接更多精彩內(nèi)容