Tips
《Effective Java, Third Edition》一書英文版已經出版,這本書的第二版想必很多人都讀過,號稱Java四大名著之一,不過第二版2009年出版,到現在已經將近8年的時間,但隨着Java 6,7,8,甚至9的發布,Java語言發生了深刻的變化。
在這里第一時間翻譯成中文版。供大家學習分享之用。
5. 使用依賴注入取代硬連接資源(hardwiring resources)
許多類依賴於一個或多個底層資源。例如,拼寫檢查器依賴於字典。將此類類實現為靜態實用工具類並不少見(條目 4):
// 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) { ... }
}
這兩種方法都不令人滿意,因為他們假設只有一本字典值得使用。在實際中,每種語言都有自己的字典,特殊的字典被用於特殊的詞匯表。另外,使用專門的字典來進行測試也是可取的。想當然地認為一本字典就足夠了,這是一廂情願的想法。
可以通過使dictionary
屬性設置為非final
,並添加一個方法來更改現有拼寫檢查器中的字典,從而讓拼寫檢查器支持多個字典,但是在並發環境中,這是笨拙的、容易出錯的和不可行的。靜態實用類和單例對於那些行為被底層資源參數化的類來說是不合適的。
所需要的是能夠支持類的多個實例(在我們的示例中,即SpellChecker
),每個實例都使用客戶端所期望的資源(在我們的例子中是dictionary
)。滿足這一需求的簡單模式是在創建新實例時將資源傳遞到構造方法中。這是依賴項注入(dependency injection)的一種形式:字典是拼寫檢查器的一個依賴項,當它創建時被注入到拼寫檢查器中。
// 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)和 builder模式(條目 2)。
該模式的一個有用的變體是將資源工廠傳遞給構造方法。 工廠是可以重復調用以創建類型實例的對象。 這種工廠體現了工廠方法模式(Factory Method pattern )[Gamma95]。 Java 8中引入的Supplier <T>
接口非常適合代表工廠。 在輸入上采用Supplier<T>
的方法通常應該使用有界的通配符類型( bounded wildcard type)(條目 31)約束工廠的類型參數,以允許客戶端傳入工廠,創建指定類型的任何子類型。 例如,下面是一個使用客戶端提供的工廠生成tile的方法:
Mosaic create(Supplier<? extends Tile> tileFactory) { ... }
盡管依賴注入極大地提高了靈活性和可測試性,但它可能使大型項目變得混亂,這些項目通常包含數千個依賴項。使用依賴注入框架(如Dagger[Dagger]、Guice[Guice]或Spring[Spring])可以消除這些混亂。這些框架的使用超出了本書的范圍,但是請注意,為手動依賴注入而設計的API非常適合這些框架的使用。
總之,不要使用單例或靜態的實用類來實現一個類,該類依賴於一個或多個底層資源,這些資源的行為會影響類的行為,並且不讓類直接創建這些資源。相反,將資源或工廠傳遞給構造方法(或靜態工廠或builder模式)。這種稱為依賴注入的實踐將極大地增強類的靈活性、可重用性和可測試性。