Effective Java之多个构造参数考虑用构建器(二)

静态工厂方法和构造器都有一个共同的特点–>无法扩展到大量的参数。

对于大量的参数类,我们有以下方案:
1.重叠构造器。这个源码中经常可以看到,例如HashMap:

 public HashMap(int initialCapacity, float loadFactor) {
        if (initialCapacity < 0)
            throw new IllegalArgumentException("Illegal initial capacity: " +
                                               initialCapacity);
        if (initialCapacity > MAXIMUM_CAPACITY)
            initialCapacity = MAXIMUM_CAPACITY;
        if (loadFactor <= 0 || Float.isNaN(loadFactor))
            throw new IllegalArgumentException("Illegal load factor: " +
                                               loadFactor);
        this.loadFactor = loadFactor;
        this.threshold = tableSizeFor(initialCapacity);
    }
    public HashMap(int initialCapacity) {
        this(initialCapacity, DEFAULT_LOAD_FACTOR);
    }
    public HashMap() {
        this.loadFactor = DEFAULT_LOAD_FACTOR;
    }

也就是说每个HashMap调用的其实都是含有全部参数的构造器public HashMap(int initialCapacity, float loadFactor)

这种重叠构造器的方法在参数较少的时候,是一种不错的选择,可以参数多了的话呢?
参数顺序容易混淆吧?每个参数的意义容易难看懂吧?冗余的代码写得太多了吧?

于是方案二出现了
2.javaBean模式
就是把这个类声明成javabean,只需要调用他的无参构造方法,set,set,set就好了。
这样就避免了很多重叠构造器的问题

但是问题是,set,set,set的连续使用让javabean的构造过程分开来了,让javabean处于不一致的状态(状态就是javabean的属性集合,相邻两个set方法过后的javabean属性不一样,也就是状态不一致),因此,有些不小心的程序员,在set,set,set方法之间使用了javabean,因此可能会导致错误。

有没有更好的方案呢?

3.builder设计模式
以下是一个例子
public class User {

    private final String firstName;   // 必传参数
    private final String lastName;    // 必传参数
    private final int age;            // 可选参数
    private final String phone;       // 可选参数
    private final String address;     // 可选参数

    private User(UserBuilder builder) {
        this.firstName = builder.firstName;
        this.lastName = builder.lastName;
        this.age = builder.age;
        this.phone = builder.phone;
        this.address = builder.address;
    }

    public String getFirstName() {
        return firstName;
    }

    public String getLastName() {
        return lastName;
    }

    public int getAge() {
        return age;
    }

    public String getPhone() {
        return phone;
    }

    public String getAddress() {
        return address;
    }

    public static class UserBuilder {
        private final String firstName;
        private final String lastName;
        private int age;
        private String phone;
        private String address;

        public UserBuilder(String firstName, String lastName) {
            this.firstName = firstName;
            this.lastName = lastName;
        }

        public UserBuilder age(int age) {
            this.age = age;
            return this;
        }

        public UserBuilder phone(String phone) {
            this.phone = phone;
            return this;
        }

        public UserBuilder address(String address) {
            this.address = address;
            return this;
        }

        public User build() {
            return new User(this);
        }
    }
}
  • *User*类的构造方法是私有的。也就是说调用者不能直接创建User对象。

  • *User*类的属性都是不可变的。所有的属性都添加了final修饰符,因此User是不可变的,并且在构造方法中设置了值。并且,对外只提供getters方法。

  • Builder模式使用了链式调用。可读性更佳。

  • Builder的内部类构造方法中只接收必传的参数,并且该必传的参数适用了final修饰符,可选参数没有final修饰符,因此可以用此builder修改。

调用builder构建User:

new User.UserBuilder("王", "小二")
                .age(20)
                .phone("123456789")
                .address("亚特兰蒂斯大陆")
                .build();

Builder模式好处和优点:

使用Builder模式必然会导致写两遍相关属性的代码和SETTER方法,看起来有点吃力不讨好。

然而需要看到的是,客户端代码的可用性和可读性得到了大大提高。与此同时,构造函数的参数数量明显减少调用起来非常直观。

用Builder的构造函数而不是set方法传递客户需要的属性。这样做的好处在于,对象总是能被一次完整的实例化,这能避免对象不一致状态带来可能的错误。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值