Java 解决构造方法参数过多-builder模式(effect java 学习笔记2)

4,677 阅读5分钟

一、前景:

  一般情况我们不会遇到这样的情况,使用静态工厂方法,或者构造方法就足够。但是它们也有一个限制就是,它们不能很好的扩展到很多可选参数的场景。随着我们业务的深入,某些java bean 中的参数将会越来越多,我们添加的构造方法也相应的增加。想想一个10个参数的构造方法,我胃痛。

  既然想要用builder模式,我们首先需要知道传统方法的不足。


二、可伸缩构造方法 VS builder模式

  我们都用代码实例来说话

1、可伸缩构造方法

public class Student {
    private final String name; // required
    private final String sex;  // required
    private final int weight;  // optional
    private final int height;  // optional 
    private final int age;     // optional

    public Student(String name, String sex) {
        this(name, sex, 0);
    }
    
    public Student(String name, String sex, int w) {
        this(name, sex, w, 0);
    }
    
    public Student(String name, String sex, int w, int h) {
        this(name, sex, w, h, 0);
    }

    public Student(String name, String sex, int w, int h, int a) {
        this.name = name;
        this.sex = sex;
        this.weight = w;
        this.height = h;
        this.age = a;
    }
}

当我们想创建一个Student实例的时候,可以设置所有参数的最短的够着方法如下:

Student student = new Student("小明", "男", 50, 150, 16);

  通常情况下,这个构造方法可能需要许多你不想设置的参数(是不是有点不爽),随着参数的不断增加,它很快会失控的。(你也会发疯的)。首先我们很难读懂这个方法,其次如果反转了两个参数,编译器是不会报错的。比如身高和体重填反了,糟心。。。。。

2、JavaBean模式

  当在构造方法中遇到许多可选参数时,另一种选择是 JavaBeans 模式,在这种模式中,调用一个无参数的构造函 数来创建对象,然后调用 setter 方法来设置每个必需的参数和可选参数:

public class Student {
    private  String name; // required
    private  String sex;  // required
    private  int weight;  // optional
    private  int height;  // optional
    private  int age;     // optional

    public Student() {}


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

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

    public void setWeight(int weight) {
        this.weight = weight;
    }

    public void setHeight(int height) {
        this.height = height;
    }

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

这种模式没有伸缩构造方法的缺点。有点冗长,但创建实例容易,并且容易阅读

        Student student = new Student();
        student.setName("小明");
        student.setSex("男");
        student.setAge(16);
        student.setWeight(50);
        student.setHeight(150);

  JavaBeans 模式本身有严重的缺陷。由于构造方法在多次调用中被分割,所以在构造过程中 JavaBean 可能处于不一致的状态。该类没有通过检查构造参数参数的有效性来执行一致性的选项。在不一致的状态下尝试使用 对象可能会导致与包含 bug 的代码大相径庭的错误,因此很难调试。一个相关的缺点是,JavaBeans 模式排除了让类 不可变的可能性,并且需要在程序员的部分增加工作以确保线程安全

3、builder模式

  builder 模式结合了可伸缩构造方法模式的安全性和JavaBean模式的可读性。

public class Student {
    private final String name;
    private final String sex;
    private  final int weight;
    private final int height;
    private  final int age;

    private Student(Builder builder) {
        this.name = builder.name;
        this.sex = builder.sex;
        this.weight = builder.weight;
        this.height = builder.height;
        this.age = builder.age;
    }
    
    public static class Builder {
        private final String name; // required
        private final String sex;  // required
        private int weight;  // optional
        private int height;  // optional
        private int age;     // optional

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

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

        public Builder setHeight(int height) {
            this.height = height;
            return this;
        }
        
        public Builder setAge(int age) {
            this.age = age;
            return this;
        }
        
        public Student build() {
            return new Student(this);
        }

    }
}

  我首先来喷一下,代码量翻倍了。但是Student 类是不可变的,所有的参数默认值都在一个地方。builder 的 setter 方法返回 builder 本身, 这样调用就可以被链接起来,从而生成一个流畅的 API。实例如下:

Student student = new Student.Builder("小明", "男").setWeight(50)
                .setHeight(150).setAge(16).build();

  代码很容易编写,更重要的是易于阅读。 Builder 模式模拟 Python 和 Scala 中的命名可选参数。 这里没有涉及到有效性的检查


总结:

  builder 对构造方法的一个微小的优势是,builder 可以有多个可变参数,因为每个参数都是在它自己的方法中指 定的。

  Builder 模式非常灵活。 单个 builder 可以重复使用来构建多个对象。 builder 的参数可以在构建方法的调用之间 进行调整,以改变创建的对象。 builder 可以在创建对象时自动填充一些属性,例如每次创建对象时增加的序列号。

  Builder 模式也有缺点。为了创建对象,首先必须创建它的 builder。虽然创建这个 builder 的成本在实践中不太可 能被注意到,但在性能关键的情况下可能会出现问题。而且,builder 模式比伸缩构造方法模式更冗长,因此只有在 有足够的参数时才值得使用它,比如四个或更多。但是请记住,如果希望在将来添加更多的参数。但是,如果从构造 方法或静态工厂开始,并切换到 builder,当类演化到参数数量失控的时候,过时的构造方法或静态工厂就会面临尴 尬的处境。因此,所以,最好从一开始就创建一个 builder。

  总而言之,当设计类的构造方法或静态工厂的参数超过几个时,Builder 模式是一个不错的选择,特别是如果许 多参数是可选的或相同类型的。客户端代码比使用伸缩构造方法(telescoping constructors)更容易读写,并且 builder 比 JavaBeans 更安全。

ps:如果有微信读书的书友,可以来微信读书群传送门找组织。