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

327 阅读3分钟

静态工厂方法和构造方法的局限性

当创建Java对象实例时,静态工厂方法和构造方法都有一个共同的局限性:不能友好的扩展到很多可选参数的场景。如关于一个学生的信息:

public class StudentInfo {
    private String stuCode; // 学号-必选
    private String stuName; // 姓名-必选
    private String sex;     // 性别-非必选
    private Integer age;    // 年龄-非必选
    private String telephone; // 手机号-非必选
    private String address;   // 地址-非必选
    // 非必选...
}

重载构造函数模式

比较常见,在JDK源码中也随处可见,直接上代码:

public StudentInfo(String stuCode, String stuName) {
    this(stuCode, stuName, null);
}
public StudentInfo(String stuCode, String stuName, String sex) {
    this(stuCode, stuName, sex, null);

}
public StudentInfo(String stuCode, String stuName, String sex, Integer age) {
    this(stuCode, stuName, sex, age, null);

}
public StudentInfo(String stuCode, String stuName, String sex, Integer age, String telephone) {
    this(stuCode, stuName, sex, age, telephone, null);
}
public StudentInfo(String stuCode, String stuName, String sex, Integer age, String telephone, String address) {
    this.stuCode = stuCode;
    this.stuName = stuName;
    this.sex = sex;
    this.age = age;
    this.telephone = telephone;
    this.address = address;
}

当想要创建实例时,就选可用的参数列表最短的构造方法:

StudentInfo student = new Student("100", "老王", "男", 18);

总结:重载构造方法模式可行,但是当有许多参数的时候,客户端代码会很难编写,并且仍然较难以阅读。

JavaBeans模式

在这种模式中,调用一个无参数的构造函数来创建对象,然后调用setter方法来设置每个必需的参数和可选参数:

StudentInfo student = new StudentInfo();
student.setStuCode("100");
student.setStuName("老王");
student.setSex("男");
student.setAge(18);

总结:JavaBeans模式创建实例很容易,可读性也很高,但有着严重的缺点,在构造过程中JavaBean可能处于不一致的状态(创建对象和赋值分开了,使用处于不一致状态的对象可能会导致出错)。

建造者builder模式

构建者builder模式能保证像重载构造函数模式那样的安全性,也能保证JavaBeans模式那么好的可读性。实现方法是在所构建的类中写一个静态内部类ExampleBuilder,如下:

public class StudentInfo {
    private String stuCode; // 学号-必选
    private String stuName; // 姓名-必选
    private String sex;     // 性别-非必选
    private Integer age;    // 年龄-非必选
    private String telephone; // 手机号-非必选
    private String address;   // 地址-非必选

    public StudentInfo(String stuCode, String stuName, String sex, Integer age, String telephone, String address) {
        this.stuCode = stuCode;
        this.stuName = stuName;
        this.sex = sex;
        this.age = age;
        this.telephone = telephone;
        this.address = address;
    }
    public static StudentInfoBuilder builder(String stuCode, String stuName) {
        return new StudentInfoBuilder(stuCode, stuName);
    }

    public static class StudentInfoBuilder {
        private String stuCode;
        private String stuName;
        private String sex;
        private Integer age;
        private String telephone;
        private String address;

        public StudentInfoBuilder(String stuCode, String stuName) {
            this.stuCode = stuCode;
            this.stuName = stuName;
        }
        public StudentInfoBuilder sex(String sex) {
            this.sex = sex;
            return this;
        }
        public StudentInfoBuilder age(Integer age) {
            this.age = age;
            return this;
        }
        public StudentInfoBuilder telephone(String telephone) {
            this.telephone = telephone;
            return this;
        }
        public StudentInfoBuilder address(String address) {
            this.address = address;
            return this;
        }
        public StudentInfo build() {
            return new StudentInfo(this.stuCode, this.stuName, this.sex, this.age, this.telephone, this.address);
        }
    }
}

使用简单,可读性高,如下:

StudentInfo student = StudentInfo.builder("100","老王")
        .sex("男")
        .age(18).build();

总结:如果类的构造方法或静态工厂方法中具有多个参数时,设计这种类时,builder模式是一种不错的选择!(tips:为了创建对象,就必须先创建它的ExampleBuilder,增加了一点额外开销)

扩展:你可能发现了builder模式代码更加冗长,不要担心,它来啦:lombok的@Builder注解,内部原理一模一样,省去了我们的大部分搬砖时间!👍👍👍