許多類依賴於一個或多個底層資源。 例如,拼寫檢查器依賴於字典。常見的作法是將這些類實現爲靜態實用程序類(第4項):java
// Inappropriate use of static utility - inflexible & untestable! public class SpellChecker { private static final Lexicon dictionary = ...; private SpellChecker() {} // Noninstantiable public static boolean isValid(String word) { ... } public static List<String> suggestions(String typo) { ... } }
一樣的,將它們做爲單例實現的狀況並很多見(第3項):併發
// Inappropriate use of singleton - inflexible & untestable! 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) { ... } }
這些方法都不使人滿意,由於它們假設只有一本值得使用的字典。 在實踐中,每種語言都有本身的字典,特殊字典用於特殊詞彙。 並且,可能須要使用特殊字典進行測試。 假設單本字典就足以知足全部狀況,這是一廂情願的想法。app
你能夠嘗試讓SpellChecker支持多個詞典,方法是使字典字段爲非final域,並添加一個方法來更改現有拼寫檢查器中的字典,但這在併發時設置會很笨拙,容易出錯而且不可行。 靜態實用程序類和單例不適用於底層資源做爲參數的類(Static utility classes and singletons are inappropriate for classes whose behavior is parameterized by an underlying resource.)。框架
所須要的是可以支持類的多個實例(在咱們的示例中爲SpellChecker),每一個實例都使用客戶端所需的資源(在咱們的示例中爲字典)。 知足此要求的簡單模式是在建立新實例時將資源傳遞給構造函數。 這是依賴注入的一種形式:字典是拼寫檢查器的依賴項,並在建立時注入拼寫檢查器。ide
// Dependency injection provides flexibility and testability 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) { ... } }
這種依賴注入很簡單,以致於程序猿用了不少年殊不知道它有一個名稱。雖然咱們的拼寫檢查器只有一個資源(字典),可是依賴注入可使用任意數量的資源和任意的依賴關係,它保留了不變性(第17項),所以多個客戶端能夠共享依賴對象(假設客戶端須要相同的底層資源)。依賴注入一樣適用於構造函數、靜態工廠(第1項)和構建器(第2項)。函數
將資源工廠傳遞給構造函數就會變成一個有用的模式。工廠是一個對象,經過重複調用這個工廠能夠建立某個類型的實例對象。這些就是工廠方法模式 [Gamma95]。Java 8中引入的Supplier <T>接口很是適合體現工廠。在輸入上採用Supplier <T>的方法一般應該使用泛型(第31項)約束工廠的類型參數,以容許客戶端傳入建立指定類型的任何子類型的工廠。例如,這是一種使用客戶提供的工廠生成馬賽克來生成每一個圖塊的方法:測試
Mosaic create(Supplier<? extends Tile> tileFactory) { ... }
儘管依賴注入極大地提升了靈活性和可測試性,但它可能會使大型項目更加混亂,這些項目一般包含數千個依賴項。經過使用依賴注入框架,例如Dagger [Dagger],Guice [Guice]或Spring [Spring],能夠消除這種混亂。這些框架的使用超出了本書的範圍,但請注意,爲手動依賴注入而設計的API能夠輕鬆地適用於這些框架。flex
總之,若是有一個類依賴一個或多個底層資源的類,而且底層資源類影響了類的行爲,不要使用單例或靜態實用程序類來實現它,而且不要讓類直接建立這些資源(do not use a singleton or static utility class to implement a class that depends on one or more underlying resources whose behavior affects that of the class)。相反,將資源或工廠傳遞給構造函數(或靜態工廠或構建器)。這種作法稱爲依賴注入,將極大地加強類的靈活性,可重用性和可測試性。ui