Effective Java---02 当构造方法参数过多时使用builder模式

当类的构造方法参数过多时,可以使用Builder模式来提高代码可读性和安全性。Builder模式将对象构造与表示分离,允许创建不同表示的复杂对象。相比重载构造方法或JavaBeans模式,Builder模式提供了更流畅的API,避免了对象状态在构建过程中的变化错误。NutritionFacts类的示例展示了如何实现Builder模式,通过链式调用来构建对象,使得代码更清晰易懂。
摘要由CSDN通过智能技术生成

Effective Java---02 当构造方法参数过多时使用builder模式

定义

The intent of the Builder design pattern is to separate the construction of a complex object from its representation. By doing so the same construction process can create different representations.

Builder设计模式的目的是将复杂对象的构造与其表示分离开来。通过这样做,相同的构造过程可以创建不同的表示。

使用场景

灵活在工作中运用设计模式是需要一定的功底的,首先我们需要知道它的使用场景:当一个类的构造函数参数个数超过4个,而且这些参数有些是可选的参数,考虑使用构造者模式。一个基本部件不会变,而其组合经常变化的时候,也可以考虑使用。

解决问题

考虑定义一个描述食品包装上的营养成分标签的类NutritionFacts。这些标签有几个必需的属性——每次建议的摄入量servingSize,每罐的份量servings和每份卡路里calories ,以及超过 20 个可选的属性——总脂肪、饱和脂肪、反式脂肪、胆固醇、钠等等。大多数产品只包含这些可选字段中的少数,且具有非零值(大部分字段为空)。

方式一

重载很多个构造方法,即折叠构造函数模式,因为我们不清楚未来在使用NutritionFacts的过程中会使用哪些参数来构建它的对象。

/**
 * 描述食品包装上的营养成分标签
 */

public class NutritionFacts {
    private final int servingSize;  // (mL)            required
    private final int servings;     // (per container) required
    private final int calories;     // (per serving)   optional
    private final int fat;          // (g/serving)     optional
    private final int sodium;       // (mg/serving)    optional
    private final int carbohydrate; // (g/serving)     optional

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

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

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

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

    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) 中两个参数都是int类型,可能会存在参数传递混乱,但是这种情况编译器不会报错。且当参数例如上面要求的那么多,你不想那样写,我也不想那样写,说明这不是最优解。

方式二

当在构造方法中遇到许多可选参数时,另一种选择是 JavaBeans 模式,在这种模式中,调用一个无参的构造方法来创建对象,然后调用 setter 方法来设置每个必需的参数和可选参数:

public class NutritionFacts {
    private int servingSize;  // (mL)            required
    private int servings;     // (per container) required
    private int calories;     // (per serving)   optional
    private int fat;          // (g/serving)     optional
    private int sodium;       // (mg/serving)    optional
    private int carbohydrate; // (g/serving)     optional

	 public NutritionFacts(){}

     public NutritionFacts(int servingSize, int servings) {
        this.servingSize = servingSize;
        this.servings = servings;
    }
    
     public int getServingSize() {
        return servingSize;
    }

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

    public int getServings() {
        return servings;
    }

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

    public int getCalories() {
        return calories;
    }

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

    public int getFat() {
        return fat;
    }

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

    public int getSodium() {
        return sodium;
    }

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

    public int getCarbohydrate() {
        return carbohydrate;
    }

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

在使用时可以先创建对象,再用set方法为其赋值。
**缺点:**构建过程中对象的状态容易发生变化,造成错误。因为NutritionFacts类中的属性是分步设置的,没有强制的一致性措施,所以就可能出错。

builder模式

  1. 在NutritionFacts 中创建一个静态内部类 Builder,然后将NutritionFacts中的参数都复制到Builder类中;
  2. 在NutritionFacts中创建一个private的构造函数,参数为Builder类型;
  3. 在Builder中创建一个public的构造函数,参数为NutritionFacts中必填的参数;
  4. 在Builder中创建设置函数,对NutritionFacts中那些可选参数进行赋值,返回值为Builder类型的实例;
  5. 在Builder中创建一个build()方法,在其中构建NutritionFacts的实例并返回.
/**
 * 1. 在NutritionFacts 中创建一个静态内部类 Builder,然后将NutritionFacts中的参数都复制到Builder类中;
 */
public class NutritionFacts {
    private final int servingSize;  // (mL)            required
    private final int servings;     // (per container) required
    private final int calories;     // (per serving)   optional
    private final int fat;          // (g/serving)     optional
    private final int sodium;       // (mg/serving)    optional
    private final int carbohydrate; // (g/serving)     optional

    /**
     * 2. 在NutritionFacts中创建一个private的构造函数,参数为Builder类型;
     */
    private NutritionFacts(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;  // (mL)            required
        private final int servings;     // (per container) required

        private int calories = 0;     // (per serving)   optional
        private int fat = 0;          // (g/serving)     optional
        private int sodium = 0;       // (mg/serving)    optional
        private int carbohydrate = 0; // (g/serving)     optional

        /**
         * 3. 在Builder中创建一个public的构造函数,参数为NutritionFacts中必填的参数;
         */
        public Builder(int servingSize, int servings) {
            this.servingSize = servingSize;
            this.servings = servings;
        }

        /**
         * 4. 在Builder中创建设置函数,对NutritionFacts中那些可选参数进行赋值,返回值为Builder类型的实例;
         */
        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) {
            carbohydrate = val;
            return this;
        }

        /**
         * 5. 在Builder中创建一个build()方法,在其中构建NutritionFacts的实例并返回.
         */
        public NutritionFacts build() {
            return new NutritionFacts(this);
        }
    }
    //省略getter方法......
 @Override
    public String toString() {
        return "NutritionFacts02{" +
                "servingSize=" + servingSize +
                ", servings=" + servings +
                ", calories=" + calories +
                ", fat=" + fat +
                ", sodium=" + sodium +
                ", carbohydrate=" + carbohydrate +
                '}';
    }
}

如何在客户端使用

NutritionFacts 类是不可变的,所有的参数默认值都在一个地方。builder 的 setter 方法返回 builder 本身,这样就可以进行链式调用,从而生成一个流畅的 API。

NutritionFacts cocaCola = new NutritionFacts.Builder(240, 8)
    .calories(100).sodium(35).carbohydrate(27).build();

总而言之,当设计类的构造方法或静态工厂的参数超过几个时,Builder 模式是一个不错的选择,特别是许多参数是可选的或相同类型的。builder 模式客户端代码比使用伸缩构造方法(telescoping constructors)更容易读写,并且 builder 模式比 JavaBeans 更安全。

参考文章链接:

  1. https://github.com/wangdecheng/effective-java-3rd-chinese/blob/master/docs/notes/02.%20%E5%BD%93%E6%9E%84%E9%80%A0%E6%96%B9%E6%B3%95%E5%8F%82%E6%95%B0%E8%BF%87%E5%A4%9A%E6%97%B6%E4%BD%BF%E7%94%A8builder%E6%A8%A1%E5%BC%8F.md
  2. https://zhuanlan.zhihu.com/p/58093669
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值