原文:http://blog.csdn.net/nugongahou110 https://blog.csdn.net/nugongahou110/article/details/50395698
builder设计模式我们很常见,比如我们使用AlertDialog的时候就使用的builder设计模式,著名的Universal-Image-Loader的初始化配置也是使用的builder设计模式,那么他们为什么使用builder设计模式,什么情况下我们应该考虑使用builder设计模式,这是我们很多人的疑惑,即便是学会了也不知道什么时候应该使用,这篇文章我将告诉大家在什么情况下应该考虑使用builder设计模式,如果不用的话会有什么坏处,用了会有什么好处。

如果我们有一个类Student,他有很多的属性,但是仅仅姓名和学号是必须赋值的,其他的属性都是可选项,比如像下面代码中所示

public class Student {
    private final int stuId;//必须
    private final String name;//必须
    private final int age;//可选
    private final int gender;//可选
    private final int address;//可选
    ...//还有很多可选属性
}

那么我们怎么来创建一个Student对象呢?我们看到每个属性都用final来修饰了,说明每个属性都要在构造方法中被初始化,我们又必须提供各种参数数量的构造方法,我们看如下代码

public class Student {
    private final int stuId;//必须
    private final String name;//必须
    private final int age;//可选
    private final int gender;//可选
    private final String address;//可选

    public Student(int stuId,String name){
        this(stuId,name,0,1,"");
    }
    public Student(int stuId,String name,int age){
        this(stuId,name,age,1,"");
    }
    public Student(int stuId,String name,int age,int gender){
        this(stuId,name,age,gender,"");
    }
    public Student(int stuId,String name,int age,int gender,String address){
        this.stuId = stuId;
        this.name = name;
        this.age = age;
        this.gender = gender;
        this.address = address;
    }
}

这样做确实可以解决我们的需求,但这还是可选参数不多的情况,如果有很多可选参数,我们就必须要写很多个构造函数,这将导致代码的可读性和维护性变差,更重要的是,当我们要用到这个类的时候会感觉无从下手,我到底应该用哪个构造方法呢?应该用两个参数的构造方法还是用三个参数的呢?如果我用两个参数的构造方法,那么可选参数的默认值是多少?

更棘手的是,如果我只想给Student对象设置address属性而不设置age和gender属性的话怎么办?我们显然还得再继续添加构造方法,或者我们只能调用全参的构造方法,然后给age和gender属性设置个默认值。

还有一点,我们看到stuId,age,gender都是int类型的,那么我们在创建Student对象时,哪一个int类型的对象代表stuId,哪一个代表age,这还进一步增加了使用成本。

那么我们还有没有其他的办法?答案是有!我们可以只设置一个默认的无参构造方法,然后给每个属性添加getter和setter方法,代码如下

public class Student {
    private int stuId;//必须
    private String name;//必须
    private int age;//可选
    private int gender;//可选
    private String address;//可选

    public Student(){

    }

    public int getStuId() {
        return stuId;
    }

    public void setStuId(int stuId) {
        this.stuId = stuId;
    }

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }

    public int getAge() {
        return age;
    }

    public void setAge(int age) {
        this.age = age;
    }

    public int getGender() {
        return gender;
    }

    public void setGender(int gender) {
        this.gender = gender;
    }

    public String getAddress() {
        return address;
    }

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

这种方法看上去可读性和维护性比较好,当我们使用这个类的时候只需要创建一个空的对象并且设置我们需要的属性就可以了。比如这样:

 Student stu = new Student();
 stu.setStuId(1);
 stu.setName("小明");
 stu.setAge(12);

这样做有两个问题,第一个问题是我们的stu对象没有一个创建完毕的标识,上面的stu对象我们设置了三个属性,但当别人看到这段代码时,他不确定这个stu对象是只需要这三个属性还是当时作者忘了写完整,除非所有的属性都给set上,别人才能确保你这个对象创建完毕;另一个问题是任何人都可以在我们创建好的基础上继续改变它,也就是继续给它set新的属性或者删除某个已经set的属性,这就会使我们的stu对象具有可变性,这会引起潜在的风险。

好在我们还有第三种方法,那就是builder设计模式了。

public class Student {
    private final int stuId;//必须
    private final String name;//必须
    private final int age;//可选
    private final int gender;//可选
    private final String address;//可选

    private Student(StudentBuilder builder){
        this.stuId = builder.stuId;
        this.name = builder.name;
        this.age = builder.age;
        this.gender = builder.gender;
        this.address = builder.address;
    }

    public int getStuId() {
        return stuId;
    }

    public String getName() {
        return name;
    }

    public int getAge() {
        return age;
    }

    public int getGender() {
        return gender;
    }

    public String getAddress() {
        return address;
    }

    public static class StudentBuilder{
        private final int stuId;
        private final String name;
        private int age;
        private int gender;
        private String address;

        public StudentBuilder(int stuId,String name){
            this.stuId = stuId;
            this.name = name;
        }
        public StudentBuilder setAge(int age){
            this.age = age;
            return this;
        }
        public StudentBuilder setGender(int gender){
            this.gender = gender;
            return this;
        }
        public StudentBuilder setAddress(String address){
            this.address = address;
            return this;
        }
        public Student build(){
            return new Student(this);
        }
    }

}

值得注意的几点:
1.Student的构造方法是私有的,也就是说我们不能直接new出Student对象
2.我们又将Student的属性用final修饰了,并且我们在构造方法中都为他们进行了初始化操作,我们只提供了getter方法
3.使用builder模式构造出来的对象有更好的可读性,等下我们会看到
4.StudentBuilder的属性中只给我们必须的属性添加的final修饰,所以我们必须在StudentBuilder的构造方法中为他们初始化

使用builder设计模式完美的解决了方法一和方法二的不足,并且兼具他们的优点:具有必填属性和可选属性的区分,更重要的是:可读性很强。唯一的不足是我们要在StudentBuilder中重复的写一遍Student中的属性。

好,现在我们来创建一个Student对象吧

public Student getStudent(){
        return new Student.StudentBuilder(1,"小明")//必填属性在构造方法中赋值
                    .setAge(1)//设置可选属性 年龄
                    .setGender(1)//设置可选属性 性别 默认1为男
                    .build();//对象构建完毕的标识,返回Student对象
    }

非常优雅有木有?他是一个链式的调用,我们可以1行代码就搞定,更重要的是,他的可读性非常强,而且通过build()我们可以很明确的告诉别人我们的Student已经创建完毕。

builder设计模式非常灵活,一个builder可以创建出各种各样的对象,我们只需要在build()之前调用set方法来为我们的对象赋值。

builder模式另一个重要特性是:它可以对参数进行合法性验证,如果我们传入的参数无效,我们可以抛出一个IllegalStateException异常,但是我们在哪里进行参数合法性验证也是有讲究的:那就是在对象创建之后进行合法性验证。我们修改StudentBuilder的build()方法

 public Student build(){
            Student student = new Student(this);
            if (student.getAge()>120){
                throw  new IllegalStateException("年龄超出限制");
            }
            return student;
        }

为什么要先创建对象,再进行参数验证?因为我们的StudentBuilder是线程不安全的,如果我们先进行参数验证后创建对象,那么创建对象的时候对象的属性可能已经被其他线程改变了,例如下面的代码就是错误的

  /**
         * 错误的
         */
        public Student build(){
            if (age>120){
                throw  new IllegalStateException("年龄超出限制");
            }
            return new Student(this);
        }

最后总结一下builder设计模式:当我们的类中有很多属性的时候,更重要的是有很多可选属性的时候,我们就可以使用builder设计模式,因为这样不仅可以使我们的类使用起来很优雅,而且还可以给我们的对象一个创建完成的标识,即build()方法。

Logo

华为开发者空间,是为全球开发者打造的专属开发空间,汇聚了华为优质开发资源及工具,致力于让每一位开发者拥有一台云主机,基于华为根生态开发、创新。

更多推荐