前言:
Chapter 2. Creating and Destroying Objects
- 01. 考慮使用靜態工廠方法替代構造方法
- 02. 當構造方法參數過多時使用builder模式
- 03. 使用私有構造方法或枚類實現Singleton屬性
- 04. 使用私有構造方法執行非實例化
- 05. 依賴注入優於硬連接資源(hardwiring resources)
- 06. 避免創建不必要的對象
01. 考慮使用靜態工廠方法替代構造方法
靜態工廠方法(不同於工廠方法模式):一個類可以提供一個公共靜態工廠方法,它只是一個返回類實例的靜態方法。
<code>public static Boolean valueOf(boolean b) { return b ? Boolean.TRUE : Boolean.FALSE;}/<code>
靜態工廠方法優點:
- 與構造方法不同,它們是有名字的。
- 與構造方法不同,它們不需要每次調用時都創建一個新對象。
- 與構造方法不同,它們可以返回其返回類型的任何子類型的對象。
- 返回對象的類可以根據輸入參數的不同而不同。
- 在編寫包含該方法的類時,返回的對象的類不需要存在。
靜態工廠方法缺點:
- 只提供靜態工廠方法的主要限制:沒有公共或受保護構造方法的類不能被子類化。
- 程序員很難找到它們。
總結:
- 靜態工廠方法和公共構造方法都有它們的用途,並且瞭解它們的相對優點是值得的。
- 通常,靜態工廠更可取,因此避免在沒有考慮靜態工廠的情況下直接選擇使用公共構造方法。
02. 當構造方法參數過多時使用builder模式
靜態工廠和構造方法都有一個限制:它們不能很好地擴展到很多可選參數的情景。
很多可選參數場景的可選方案:
- 可伸縮(telescoping constructor)構造方法模式
- JavaBeans 模式
- Builder 模式(最優方案)
a. 可選方案1:可伸縮(telescoping constructor)構造方法模式:
<code>NutritionFacts cocaCola = new NutritionFacts(240, 8, 100);NutritionFacts cocaCola = new NutritionFacts(240, 8, 100, 0, 35, 27);/<code>
在這種模式中,首先提供一個只有必需參數的構造方法,接著提供增加了一個可選參數的構造函數,然後提供增加了兩個可選參數的構造函數,等等,最終在構造函數中包含所有必需和可選參數。
方案優缺點:
可伸縮構造方法模式是有效的,但是當有很多參數時,很難編寫客戶端代碼,而且很難讀懂它。
b.方案2:JavaBeans 模式:
<code>NutritionFacts cocaCola = new NutritionFacts(); cocaCola.setCalories(100); cocaCola.setSodium(35);/<code>
在這種模式中,調用一個無參的構造方法來創建對象,然後調用 setter 方法來設置每個必需的參數和可選參數。
方案優缺點:
- 優點:這種模式沒有伸縮構造方法模式的缺點。有點冗長,但創建實例很容易,並且易於閱讀所生成的代碼:
- 缺點:JavaBeans 模式本身有嚴重的缺陷。由於構造方法被分割成了多次調用,所以在構造過程中 JavaBean 可能處於不一致的狀態。該類沒有通過檢查構造參數參數的有效性來強制一致性的選項。在不一致的狀態下嘗試使用對象可能會導致一些錯誤,這些錯誤與平常代碼的BUG很是不同,因此很難調試。
c. 方案3:Builder 模式(最優方案)
<code>NutritionFacts cocaCola = new NutritionFacts.Builder(240, 8).calories(100).sodium(35).carbohydrate(27).build();/<code>
客戶端不直接構造所需的對象,而是調用一個包含所有必需參數的構造方法 (或靜態工廠)得到獲得一個 builder 對象。然後,客戶端調用 builder 對象的與 setter 相似方法來設置你想設置的可選參數。最後,客戶端調用builder對象的一個無參的 build 方法來生成對象,該對象通常是不可變的。
總結:
- 當設計類的構造方法或靜態工廠的參數超過幾個時,Builder 模式是一個不錯的選擇,特別是如果許多參數是可選的或相同類型的。
- builder模式客戶端代碼比使用伸縮構造方法(telescoping constructors)更容易讀寫,並且builder模式比 JavaBeans 更安全。
03. 使用私有構造方法或枚類實現Singleton屬性
單例是一個僅實例化一次的類[Gamma95]。單例對象通常表示無狀態對象,如函數 (條目 24) 或一個本質上唯一的系統組件。
實現單例的方法:
- public static final Elvis INSTANCE
- public static Elvis getInstance()
- 聲明單一元素的枚舉類
<code>方法1:// Singleton with public final fieldpublic class Elvis { public static final Elvis INSTANCE = new Elvis(); private Elvis() { ... }}方法2:// Singleton with static factorypublic class Elvis { private static final Elvis INSTANCE = new Elvis(); private Elvis() { ... } public static Elvis getInstance() { return INSTANCE; }}方法3:// Enum singleton - the preferred approachpublic enum Elvis { INSTANCE;}/<code>
04. 使用私有構造方法執行非實例化
工具類(utility classes)不是設計用來被實例化的,因為實例化對它沒有任何意義。
可以通過包含一個私有構造器來實現類的非實例化:
<code>// Noninstantiable utility classpublic class UtilityClass { // Suppress default constructor for noninstantiability private UtilityClass() { throw new AssertionError(); }}/<code>
05. 依賴注入優於硬連接資源(hardwiring resources)
許多類依賴於一個或多個底層資源。例如,拼寫檢查器依賴於字典。
正確做法(依賴注入):
在創建新實例時將資源傳遞到構造器中。 這是依賴項注入(dependency injection)的一種形式:字典是拼寫檢查器的一個依賴項,當它創建時被注入到拼寫檢查器中。
<code>// Dependency injection provides flexibility and testabilitypublic 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) { ... }}/<string>/<code>
06. 避免創建不必要的對象
在每次需要時重用一個對象而不是創建一個新的相同功能對象通常是恰當的。重用可以更快更流行。
場景1:如果對象是不可變的(詳見第 17 條),它總是可以被重用。
String s = new String("bikini"); // DON'T DO THIS!
String s = "bikini";
場景2:通過使用靜態工廠方法(詳見第 1 條)和構造器,可以避免創建不需要的對象。
例如,工廠方法 Boolean.valueOf(String) 比構造方法 Boolean(String) 更可取,後者在 Java 9 中被棄用。構造方法每次調用時都必須創建一個新對象,而工廠方法永遠不需要這樣做,在實踐中也不需要。
場景3:自動裝箱(auto boxing)
另一種創建不必要的對象的方法是自動裝箱(auto boxing),它允許程序員混用基本類型和包裝的基本類型,根據需要自動裝箱和拆箱。
<code>// Hideously slow! Can you spot the object creation?private static long sum() { Long sum = 0L; for (long i = 0; i <= Integer.MAX_VALUE; i++) sum += i; return sum;}/<code>
這個教訓很明顯:優先使用基本類型而不是裝箱的基本類型,也要注意無意識的自動裝箱。
總結:「當你應該重用一個現有的對象時,不要創建一個新的對象」
閱讀更多 編程家園 的文章