第02条 遇到构造方法多参数时要考虑用Buildle模式

238 阅读4分钟

为什么要使用构建器(Buildle模式)

静态工厂和构造方法都有共同的局限性:不能很好的扩展到大量的可选参数。


模拟一个场景:
一个Person对象,它有2个必要的参数(name,age),2个可选参数(sex,address),我们来获取它的实例。

1、静态工厂方法实现 - 重叠构造方法

重叠构造方法的缺点:
(1) 出现很多不要的参数却必须设置默认值的的情况。
(2) 重叠构造方法可行,但是当有很多参数时,代码就会很难编写也不容易阅读,使用的时候,必须看清楚每个参数的含义。

public class Person {

    // 必要参数
    private String name;
    private int age;
    
    // 可选参数
    private String sex;
    private String address;

    public Person(String name, int age, String sex, String address) {
        this.name = name;
        this.age = age;
        this.sex = sex;
        this.address = address;
    }

    public static Person normal(String name, int age) {
        return new Person(name, age, "男", "北京");
    }

    public static Person normalAndSex(String name, int age, String sex) {
        return new Person(name, age, sex, "北京");
    }

    public static Person normalAndAddress(String name, int age, String address) {
        return new Person(name, age, "男", address);
    }

    public static Person normalAll(String name, int age, String sex, String address) {
        return new Person(name, age, sex, address);
    }
}
JavaBean模式实现
  • 给一个无参构造方法,然后通过setter方法去设置属性值。
  • 这种方式比重叠构造方法更容易创建对象,代码也容易阅读。
public class Person{
        // 必要参数
    private String name;
    private int age;
    
    // 可选参数
    private String sex;
    private String address;
    
    public Person(){}
    
    public void setName(String name){
        this.name = name;
    }
    
    public void setAge(int age){
        this.age = age;
    }
    
    public void setSex(String sex){
        this.sex = sex;
    }
    
    public void setAddress(String address){
        this.address = address;
    }
}


Person person = new Person();
person.setName("李三");
person.setAge(13);
person.setSex("男");
person.setAddress("浙江杭州");
JavaBean的缺点

原文:JavaBeans模式自身有着很严重的缺点。因为构造过程被分到了几个调用中,在构造过程中JavaBean可能处于不一致的状态。类无法仅仅通过检验构造器参数的有效性来保证一致性。试图使用处于不一致状态的对象,将会导致失败,这种失败与包含错误的代码大相径庭,因此它调试起来十分困难。于此相关的另一点不足在于,JavaBean模式阻止了把类做成不可变的可能,这就需要程序员付出额外的努力来确保它的线程安全。

对上文的理解:
(1) 你创建了一个Person类,提供了无参构造和set/get方法。调用者实例化Person类,但是调用者并不清楚需要为new出来的对象set哪些该有的属性,从而导致就算new出来了对象,这个对象也不能被很好的使用。另外,同时new出来多个对象,每个对象都设置不同的属性,也会导致这几个对象不一致,在你get到未set的属性时,也会出现错误。
(2) 同一个对象,在多线程中,线程A获取这个对象调用这个对象get方法,线程B获取这个对象调用这个对象的set方法,由于线程是谁先抢占CPU谁就先执行,所以会出现你不想要的效果。JavaBean模式并不是一种线程安全的方式。


构建器(Builder)模式

Builder模式:不直接生成想要的对象,而是利用所有必要的参数调用构造方法或静态工厂,得到一个builder对象。然后再builder对象上调用类似于setter的方法,来设置每个可选的参数。最后调用无参的build方法来生成不可变的对象。

构建器模式的优点:保证了重叠构造方法模式的安全性,也保证了JavaBeans模式的可读性。

public class Person {

    // 必要参数
    private String name;
    private int age;

    // 可选参数
    private String sex;
    private String address;

    public static class Builder {
        private String name;
        private int age;
        // 可选参数设置默认值
        private String sex = null;
        private String address = null;

        // Builder构造方法添加必要的参数
        public Builder(String name, int age) {
            this.name = name;
            this.age = age;
        }

        public Builder setSex(String sex) {
            this.sex = sex;
            return this;
        }

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

        // build()返回一个对象
        public Person build() {
            return new Person(this);
        }
    }

    // 在这里可以检查每个参数的合法性
    private Person(Builder builder) {
        this.name = builder.name;
        this.age = builder.age;
        this.sex = builder.sex;
        this.address = builder.address;
    }
}
Person person = new Person.Builder("李三", 13)
    .setSex("男").setAddress("浙江杭州")
    .build();