Try using it in your preferred language.

English

  • English
  • 汉语
  • Español
  • Bahasa Indonesia
  • Português
  • Русский
  • 日本語
  • 한국어
  • Deutsch
  • Français
  • Italiano
  • Türkçe
  • Tiếng Việt
  • ไทย
  • Polski
  • Nederlands
  • हिन्दी
  • Magyar
translation

这是AI翻译的帖子。

제이온

[Effective Java] 項目 2. 若建構函式有許多參數,請考慮使用建構器

  • 写作语言: 韓国語
  • 基准国家: 所有国家 country-flag

选择语言

  • 汉语
  • English
  • Español
  • Bahasa Indonesia
  • Português
  • Русский
  • 日本語
  • 한국어
  • Deutsch
  • Français
  • Italiano
  • Türkçe
  • Tiếng Việt
  • ไทย
  • Polski
  • Nederlands
  • हिन्दी
  • Magyar

durumis AI 总结的文章

  • 介紹了各種用於有效管理具有許多參數的建構函式的模式,並比較分析了分層建構函式模式、Java Beans 模式和建構器模式的優缺點。
  • 建構器模式具有提高程式碼可讀性和穩定性的優點,特別是與分層設計的類別一起使用時,效果更佳。
  • 建構器模式可以使程式碼更清晰,但它也具有建構器物件建立和程式碼冗長等缺點。

遞增式建構器模式

靜態工廠和建構函數在面對大量參數時都難以應對。例如,如果一個類別有 6 個欄位,而我們想在 2 個參數、3 個參數 ... 等情況下分別使用建構函數,可以使用遞增式建構器模式,如下所示。


public class NutritionFacts {

    private final int servingSize;

    private final int servings;

    private final int calories;

    private final int fat;

    private final int sodium;

    private final int carbohydrate;

    public NutritionFacts(int servingSize, int servings, int calories, int fat, int sodium, int carbohydrate) {
        this.servingSize = servingSize;
        this.servings = servings;
        this.calories = calories;
        this.fat = fat;
        this.sodium = sodium;
        this.carbohydrate = carbohydrate;
    }

    public NutritionFacts(int servingSize, int servings) {
        this(servingSize, servings, 0, 0, 0, 0);
    }

    public NutritionFacts(int servingSize, int servings, int calories) {
        this(servingSize, servings, calories, 0, 0, 0);
    }

    public NutritionFacts(int servingSize, int servings, int calories, int fat) {
        this(servingSize, servings, calories, fat, 0, 0);
    }

    public NutritionFacts(int servingSize, int servings, int calories, int fat, int sodium) {
        this(servingSize, servings, calories, fat, sodium, 0);
    }


但即使這樣,當參數太多時,在閱讀程式碼時,我們可能會混淆每個值的含義,並且可能會將類型相同的參數混淆並輸入錯誤的值。


JavaBeans 模式

JavaBeans 模式使用無參數的建構函數建立物件,然後呼叫 setter 方法設定所需的參數值。


public class NutritionFactsWithJavaBeansPattern {

    private int servingSize = -1; // 必需。沒有預設值。

    private int servings = -1; // 必需。沒有預設值。

    private int calories;

    private int fat;

    private int sodium;

    private int carbohydrate;

    public void setServingSize(int servingSize) {
        this.servingSize = servingSize;
    }

    public void setServings(int servings) {
        this.servings = servings;
    }

    public void setCalories(int calories) {
        this.calories = calories;
    }

    public void setFat(int fat) {
        this.fat = fat;
    }

    public void setSodium(int sodium) {
        this.sodium = sodium;
    }

    public void setCarbohydrate(int carbohydrate) {
        this.carbohydrate = carbohydrate;
    }


JavaBeans 模式即使在參數數量增加時,也能避免混淆地建立實例。但是,要建立一個物件,需要呼叫多個 setter 方法,並且在物件完全完成之前,一致性會被破壞。因此,類別不能是不可變的。


建構器模式

建構器模式結合了遞增式建構器模式的穩定性和 JavaBeans 模式的可讀性,是常用的模式。

客戶端不是直接建立所需的物件,而是呼叫帶有必需參數的建構函數來獲取建構器物件。然後,建構器物件提供一種 setter 方法,用於設定所需的選項參數。最後,呼叫不帶參數的 build() 方法來獲取所需的物件。


public class NutritionFactsWithBuilderPattern {

    private final int servingSize;

    private final int servings;

    private final int calories;

    private final int fat;

    private final int sodium;

    private final int carbohydrate;

    private NutritionFactsWithBuilderPattern(Builder builder) {
        servingSize = builder.servingSize;
        servings = builder.servings;
        calories = builder.calories;
        fat = builder.fat;
        sodium = builder.sodium;
        carbohydrate = builder.carbohydrate;
    }

    public static class Builder {

        private final int servingSize;

        private final int servings;

        private int calories;

        private int fat;

        private int sodium;

        private int carbohydrate;

        public Builder(int servingSize, int servings) {
            this.servingSize = servingSize;
            this.servings = servings;
        }

        public Builder calories(int val) {
            calories = val;
            return this;
        }

        public Builder fat(int val) {
            fat = val;
            return this;
        }

        public Builder sodium(int val) {
            sodium = val;
            return this;
        }

        public Builder carbohydrate(int val) {
            calories = val;
            return this;
        }

        public NutritionFactsWithBuilderPattern build() {
            return new NutritionFactsWithBuilderPattern(this);
        }
    }


Builder 類別中的建構函數接收必需參數,其餘的選項參數通過一種 setter 方法填寫。最後,通過 build() 方法建立完整的 NutritionFactsWithBuilderPattern 物件。 NutritionFactsWithBuilderPattern 類別是不可變的,因為建構器的 setter 方法返回建構器本身,因此可以連續呼叫。這種方式被稱為流暢 API 或方法鏈。


NutritionFactsWithBuilderPattern nutritionFacts =
    new NutritionFactsWithBuilderPattern.Builder(240, 8)
        .calories(100)
        .sodium(35)


從客戶端角度來看,建構器模式使程式碼更易於編寫和閱讀。


建構器模式與分層設計類別的良好搭配

public abstract class Pizza {

    public enum Topping {
        HAM, MUSHROOM, ONION, PEPPER, SAUSAGE
    }

    final Set toppings;

    Pizza(Builder builder) {
        toppings = builder.toppings.clone();
    }

    abstract static class Builder> {

        private EnumSet toppings = EnumSet.noneOf(Topping.class);

        public T addTopping(Topping topping) {
            toppings.add(topping);
            return self();
        }

        abstract Pizza build();

        protected abstract T self();
    }


Pizza.Builder 類別是一個使用遞迴類型約束的泛型類型,它通過添加抽象方法 self() 使子類別能夠在不進行類型轉換的情況下支援方法鏈。在子類別中,只需將此抽象方法的返回值設定為自身即可。

現在,讓我們看一下 Pizza 的子類別紐約披薩和卡爾佐尼披薩,體驗一下建構器模式的靈活性。


public class NyPizza extends Pizza {

    public enum Size {
        SMALL, MEDIUM, LARGE
    }

    private final Size size; // 必需參數

    private NyPizza(Builder builder) {
        super(builder);
        size = builder.size;
    }

    public static class Builder extends Pizza.Builder {

        private final Size size;

        public Builder(Size size) {
            this.size = size;
        }

        @Override
        NyPizza build() {
            return new NyPizza(this);
        }

        @Override
        protected Builder self() {
            return this;
        }
    }

}

public class CalzonePizza extends Pizza {

    private final boolean sauceInside; // 選擇參數

    private CalzonePizza(Builder builder) {
        super(builder);
        sauceInside = builder.sauceInside;
    }

    public static class Builder extends Pizza.Builder {

        private boolean sauceInside = false;

        public Builder sauceInside() {
            sauceInside = true;
            return this;
        }

        @Override
        CalzonePizza build() {
            return new CalzonePizza(this);
        }

        @Override
        protected Builder self() {
            return this;
        }
    }


每個子類別的建構器定義的 build() 類別都返回具體的子類別。子類別方法返回的類型不是父類別方法返回的類型,而是其子類型,這稱為協變返回類型化。使用此功能,客戶端不需要進行類型轉換。


NyPizza nyPizza = new NyPizza.Builder(Size.SMALL)
    .addTopping(Topping.SAUSAGE)
    .addTopping(Topping.ONION)
    .build();

CalzonePizza calzonePizza = new CalzonePizza.Builder()
    .addTopping(Topping.HAM)
    .sauceInside()


從客戶端角度來看,可以混合使用 Pizza 的枚舉和每個子類別的枚舉,並使用各自的適當方法完成物件的建立。


建構器模式的缺點

  • 需要建立建構器物件。
  • 程式碼冗長。


總結

如果建構函數或靜態工廠方法需要處理大量參數,請考慮使用建構器模式。


來源

  • Effective Java
제이온
제이온
제이온
제이온
[有效 Java] 項目 4. 要阻止實例化,請使用私有建構函數 僅包含靜態方法和欄位的工具類,最好將建構函數的存取修飾符設定為 private,以阻止實例化。 這可以防止使用者誤認為建構函數是自動生成的,並使繼承變得不可能,從而明確表示類別的意圖。

2024年4月28日

[Effective Java] 项目 5. 不要显式地使用资源,而应该使用依赖注入 如果类依赖于外部资源,则最好不要使用单例和静态实用程序类。通过依赖注入,可以提高类的灵活性、可重用性和可测试性,而使用工厂方法模式可以使依赖注入更加高效。

2024年4月28日

[Effective Java] 項目 1. 考慮使用靜態工廠方法而非建構函式 靜態工廠方法是一種比建構函式更靈活且有效率的建立實例的方法。它們可以具有名稱,並可返回符合特定條件的實例,並可透過快取來提高效能。與單例模式不同,靜態工廠方法可以建立和返回多種類型的實例,並且可以保持彈性,而無需使用反射。

2024年4月27日

[Javascript] 物件的結構 (V8) JavaScript 的 Object 在 V8 引擎中根據狀態可以被優化為類似結構體的 Fast 模式或以雜湊表運作的 Dictionary 模式。Fast 模式是針對幾乎固定形式的鍵和值進行優化,速度很快,但當新增新鍵或刪除元素等操作時, 會轉換為 Dictionary 模式,速度會變慢。
곽경직
곽경직
곽경직
곽경직
곽경직

2024年3月18日

[非理工科背景,如何成为开发者] 16. 新手开发者作品集编写技巧 新手开发者(尤其是非理工科背景)在编写作品集时,除了技术之外,还需要明确说明所开发的服务或功能。例如,在“求职者社区” 项目中,需要包含问答论坛、招聘系统、爬虫机器人开发等具体工作内容,以便面试官更好地了解项目。
투잡뛰는 개발 노동자
투잡뛰는 개발 노동자
투잡뛰는 개발 노동자
투잡뛰는 개발 노동자

2024年4月3日

[SI 開發者故事] 09. SI 專案投入後正式開始開發 SI 開發者在專案投入後會開發 RFP 中規定的功能,但由於客戶的額外需求,導致程式碼變更頻繁,效率不再重要,快速開發才是關鍵。 因此,開發應以功能實現為重點,而非乾淨程式碼或效率。
투잡뛰는 개발 노동자
투잡뛰는 개발 노동자
투잡뛰는 개발 노동자
투잡뛰는 개발 노동자
투잡뛰는 개발 노동자

2024年4月18日

邏輯數據模型 邏輯數據模型是將概念數據模型轉換為關係數據庫範式的過程,根據映射規則,將 1:1、1:N、N:M 關係 轉換為關係數據庫中的表格,並通過正規化來確保數據完整性。通過 1NF、2NF、3NF 的正規化過程,對表格進行 優化,消除部分依賴和傳遞依賴。
제이의 블로그
제이의 블로그
제이의 블로그
제이의 블로그
제이의 블로그

2024年4月9日

[非计算机专业,如何成为一名开发者] 14. 新手开发者常问的技术面试内容总结 本指南旨在为新手开发者提供技术面试准备指导。涵盖了面试中常见的概念,例如主内存区域、数据结构、关系型数据库 (RDBMS) 和 NoSQL、过程式编程和面向对象编程、重写和重载、页面替换算法、进程和线程、OSI 七层模型、TCP 和 UDP 等。
투잡뛰는 개발 노동자
투잡뛰는 개발 노동자
투잡뛰는 개발 노동자
투잡뛰는 개발 노동자

2024年4月3日

[并发] 原子操作:内存栅栏和内存顺序 这篇博文将解释在原子操作中如何考虑内存顺序,以及排序选项的重要性。 它将详细解释各种排序选项,例如 Relaxed、Acquire、Release、AcqRel 和 SecCst,以及每个选项的优缺点, 并提供使用示例代码。
곽경직
곽경직
곽경직
곽경직
곽경직

2024年4月12日