建造者模式


1. 重叠构造器模式

在这种模式下,我们提供的第一个构造器只有必要的参数,第二个构造器有一个可选参数,第三个构造器有两个可选参数,以此类推,最后一个构造器含有所有参数。

public class Person {
    private String name;    // required
    private String sex;     // required
    private Date date;      // required
    private String email;       // required

    private int height;     // optional
    private String edu;     // optional
    private String nickName;     // optional
    private int weight;     // optional
    private String addr;     // optional

    public Person(String name, String sex, Date date, String email) {
        this(name, sex, date, email, 0);
    }

    public Person(String name, String sex, Date date, String email, int height) {
        this(name, sex, date, email, height, null);
    }

    public Person(String name, String sex, Date date, String email, int height, String edu) {
        this(name, sex, date, email, height, edu, null);
    }

    public Person(String name, String sex, Date date, String email, int height, String edu, String nickName) {
        this(name, sex, date, email, height, edu, nickName, 0);
    }

    public Person(String name, String sex, Date date, String email, int height, String edu, String nickName, int
            weight) {
        this(name, sex, date, email, height, edu, nickName, weight, null);
    }

    public Person(String name, String sex, Date date, String email, int height, String edu, String nickName, int
            weight, String addr) {
        this.name = name;
        this.sex = sex;
        this.date = date;
        this.email = email;
        this.height = height;
        this.edu = edu;
        this.nickName = nickName;
        this.weight = weight;
        this.addr = addr;
    }

}

使用这种模式创建对象时,存在以下几点不足

  • 灵活性很差 : 如果客户端只想创建一个给定姓名,性别,生日,邮箱和体重,那么他将被迫调用如下构造函数,这样无意中就”被迫”设置了他本不想设置的一些参数
    public Person(String name, String sex, Date date, String email, int height, String edu, String nickName, int
              weight) {
          this(name, sex, date, email, height, edu, nickName, weight, null);
      }
    
  • 代码难以编写与阅读 : 当属性有很多的时候,代码不但看起来很丑陋,而且极易出错。试想,若客户端不小心颠倒了两个参数的顺序,编译器也不会出错,但是在运行时就会出现错误的行为,并且这种错误难以发现

    2. JavaBeans模式

    这时,我们可能转而求助于JavaBeans模式来避免这些问题,但是同时会带来一些新的问题。
  • Setter的存在妨碍的其成为不可变类的可能: 这样,在并发环境下,我们就不得不考虑其线程安全性;
  • 代码丑陋且对象易处于不一致状态: 上面创建对象的方式毕竟丑陋,同时由于对象的构造过程分为若干个函数调用,所以容易导致对象处于不一致状态。
       Person p2 = new Person();
          p2.setName("zck");
          p2.setSex("boy");
          p2.setDate(new Date());
          p2.setEmail("*******@qq.com");
          p2.setHeight(179);
          p2.setEdu("NCU");
          p2.setNickName("kang");
          p2.setWeight(140);
          p2.setAddr("武汉市");
          System.out.println(p2);
    

3. Builder模式

使用Builder模式创建复杂对象,既能保证像重叠构造器模式那样的安全性,也能保证像JavaBeans模式那么好的可读性。该模式的内涵是:不直接生成想要的对象,而是让客户端利用所有必要的参数构造一个Bulider对象,然后在此基础上,调用类似于Setter的方法来设置每个可选参数,最后通过调用无参的build()方法来生成不可变对象。一般的,所属Builder是它所构建类的静态成员类

public class Person {
    private final String name;// required
    private final Boolean sex;
    private final Integer age;

    private final Integer height;// optional
    private final Double weight;
    private final Date birthday;
    private final String addr;

    private Person(Builder builder) {
        this.name = builder.name;
        this.sex = builder.sex;
        this.age = builder.age;
        this.height =builder.height;
        this.weight = builder.weight;
        this.birthday = builder.birthday;
        this.addr = builder.addr;
    }

    public static class Builder {
        private final String name;
        private final Boolean sex;
        private final Integer age;

        private Integer height;
        private Double weight;
        private Date birthday;
        private String addr;

        public Builder(String name, Boolean sex, Integer age) {
            this.name = name;
            this.age = age;
            this.sex = sex;
        }

        // 返回Builder对象本身,链式调用
        public Builder height(Integer height) {
            this.height = height;
            return this;
        }

        public Builder weight(Double weight) {
            this.weight = weight;
            return this;
        }

        public Builder birthday(Date birthday) {
            this.birthday = birthday;
            return this;
        }

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

        // 通过Builder构建所需Person对象,并且每次都产生新的Person对象
        public Person build() {
            return new Person(this);
        }
    }

我们可以通过下面的方式来创建一个Person对象

Person.Builder builder = new Person.Builder("老张",true,23);
        Person person = builder.addr("武汉市江夏区").birthday(new Date(2008,8,8)).height(179).weight(140D).build();
  • Person类的构造方法是私有的: 也就是说,客户端不能直接创建Person对象;
  • Person类是不可变的: 所有的属性都被final修饰,在构造方法中设置参数值,并且不对外提供Setter方法
  • Bulider模式的高可读性: Builder模式使用了链式调用,可读性更佳。
  • Builder对象与目标对象的异同: Person与Buildr拥有共同的属性,并且Builder内部类的构造方法中只接收必传的参数,同时只有这些必传的参数使用final修饰符

4. Builder模式中的参数约束与线程安全行

Person对象是不可变的,因此是线程安全;但是,Builder对象并不具有线程安全性。因此,当我们需要对Person对象的参数强加约束条件时,我们应该可以对builder()方法中所创建出来的Person对象进行校验,即我们可以将builder()方法进行如下重写:

public Person build(){
        Person person = new Person(this);
        if (!"boy".equals(person.sex)){
            throw new IllegalArgumentException("所注册用户必须为男性!");
        }else{
            return person;
        }
    }

需要特别主要的是,我们是对Person对象进行参数检查,而不是对Builder对象进行参数检查,因为Builder对象不是线程安全的,即下面的代码存在线程安全问题

public Person build(){
        if (!"boy".equals(this.sex)){
            throw new IllegalArgumentException("所注册用户必须为男性!");
        }else{
            return new Person(this);
        }
    }

文章作者: kangshifu
版权声明: 本博客所有文章除特別声明外,均采用 CC BY 4.0 许可协议。转载请注明来源 kangshifu !
 上一篇
代理模式 代理模式
静态代理  静态代理:由程序员创建或特定工具自动生成源代码,也就是在编译时就已经将接口,被代理类,代理类等确定下来。在程序运行之前,代理类的.class文件就已经生成。 静态代理简单实现  例:假如一个班的同学要向老师交班费,
2018-06-26
下一篇 
接口和抽象类 接口和抽象类
抽象类我们都知道在面向对象的领域一切都是对象。同时,所有的对象都是通过类来描述的,但是并不是所有的类都是可以描述对象(对象 = 状态 + 行为)的。如果一个类没有足够的信息来描述一个具体的对象,那么我们就可以将这样的类设为抽象类。抽象类
2018-06-06
  目录