创建对象——遇到多个构造器参数时要考虑使用构建器

本文已参与「新人创作礼」活动,一起开启掘金创作之路。

  当一个类中,有一些必填属性,有一些可选属性(比如人,有必填域姓名,有选填域年龄、性别、国家等),可以采用什么方法来编写呢?本文参考《Effective Java》中 “遇到多个构造器参数时要考虑使用构建器” 给出几种方法。

1. 重叠构造器   程序员习惯使用重叠构造器(telescoping constructor)模式,在这种模式下,提供的第一个构造器只有必要的参数,第二个构造器有一个可选参数,第三个构造器有两个可选参数,依次类推,最后一个构造器包含所有可选的参数。下面有个示例。

package com.test.model;

/**
 * @description 重叠构造器
 * @date 2020/4/23 10:51
 */
public class Person {

    private String name;         // 必填属性
    private String age;          // 选填属性
    private String sex;          // 选填属性
    private String country;      // 选填属性

    public Person(String name) {
        this(name, null);
    }

    public Person(String name, String age) {
        this(name, age, null);
    }

    public Person(String name, String age, String sex) {
        this(name, age, sex, null);
    }

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

复制代码

  当创建实例时,可以根据实际参数情况,选择使用具体的构造器,如下所示。

Person person = new Person("张三");
Person person1 = new Person("李四", "12");
复制代码

  当只有一个参数、两个参数、三个参数等少量参数的时候,使用构造器传参会相对比较明确,重叠构造器模式可行。但是当有许多参数的时候,客户端代码会很难编写,并且难以阅读。对于多参的构造器,会涉及到参数顺序的问题,参数太多,难以阅读。如果客户端不小心颠倒了其中两个参数的顺序,编译器也不会出错,但是程序在运行时会出现错误的行为。

2. JavaBeans模式   遇到许多可选的构造器参数的时候,还有第二种替代方法,即 JavaBeans 模式,在这种模式下,先调用一个无参构造器来创建对象,然后再调用 setter 方法来设置每个必要的参数,以及每个相关的可选参数。

package com.test.model;

/**
 * @description JavaBeans 模式
 * @date 2020/4/23 11:24
 */
public class Person1 {

    private String name;         // 必填属性
    private String age;          // 选填属性
    private String sex;          // 选填属性
    private String country;      // 选填属性

    public Person1() {
    }
    
    public String getName() {
        return name;
    }

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

    public String getAge() {
        return age;
    }

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

    public String getSex() {
        return sex;
    }

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

    public String getCountry() {
        return country;
    }

    public void setCountry(String country) {
        this.country = country;
    }
}

复制代码

创建实例,如下所示。

Person1 person1 = new Person1();
person1.setAge("12");
复制代码

  这种模式弥补了重叠构造器模式的不足,创建实例很容易,代码可读性强。然而,Java Beans 模式自身有很严重的缺点。因为构造过程被分到了几个调用中,在构造过程中,Java Beans 可能处于不一致的状态。类无法仅仅通过检验构造器参数的有效性来保持一致性。试图使用处于不一致状态的对象将会导致失败,这种失败与包含错误的代码大相径庭,因此调试起来十分困难。与此相关的另一点不足在于,Java Beans 模式使得把类做为不可变的可能性不复存在,这就需要程序员付出额外的努力来确保它的线程安全。

  在使用 Java Bean 的时候,尤其是多线程的时候,获取同一个 Java Bean(可以通过序列/反序列获取),那么下面模拟一个场景。

线程A: 获取person,对其name age sex 就行 set 操作 同样线程B: 获取person,对其进行 get 操作 这时候会出现一种情况,在线程A中没有 set 完毕,线程 B 就开始取相应的属性。 那么就会造成 javabean 处于不一致的状态,与之相关的就是线程安全的问题,所以 javabean 作为数据的一个填充,要进行必要的保护性拷贝。

3. 建造者(Builder) 模式   建造者模式既能保证像重叠构造器模式那样的安全性,也能保证像 Java Beans 模式那么好的可读性。下面是一个建造者模式的示例。

扫描二维码关注公众号,回复: 14487876 查看本文章
package com.test.model;

public class Person2 {

    private String name;         // 必填属性
    private String age;          // 选填属性
    private String sex;          // 选填属性
    private String country;      // 选填属性

    public Person2(Builder builder) {
        this.name = builder.name;
        this.age = builder.age;
        this.sex = builder.sex;
        this.country = builder.country;
    }

    public static class Builder {
        private String name;
        private String age;
        private String sex;
        private String country;

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

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

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

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

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

        public Person2 build() {
            return new Person2(this);
        }
    }
}

复制代码

  它不直接生成想要的对象,而是让客户端利用所有必要的参数调用构造器,得到一个 builder 对象。然后客户端在 builder 对象上调用类似于 setter 的方法,来设置每个相关的可选参数。最后,客户端调用无参的 build 方法来生成通常是不可变的对象。这个 builder 通常是它构建的静态成员类。   生成对象的方法如下所示。

Person2 person2 = new Person2.Builder("张三").setSex("男").setCountry("中国").build();
Person2 person3 = new Person2.Builder("李四").setCountry("中国").build();
复制代码

  Builder 模式模拟了具名的可选参数。与构造器相比,builder 的略微优势在于,它可以有多个可变参数,因为 builder 是利用单独的方法来设置每一个参数。   Builder 模式也有它自身的不足。为了创建对象,必须先创建它的构建器。虽然创建这个构建器的开销在实践中可能不那么明显,但是在某些十分注重性能的情况下,可能就成问题了。Builder 模式还比重叠构造器模式更加冗长,因此它只有在很多参数的时候才使用,比如4个或更多参数。如果已知将来就可能需要添加更多的参数,通常一开始就使用构建器。   简而言之,如果类的构造器或者静态工厂中具有多个参数,设计这种类时,Builder 模式就是一种不错的选择,特别是当大多数参数都是可选或者类型相同的时候。与使用重叠构造器模式相比,使用 Builder 模式的客户端代码将更易于阅读和编写,构建器也比 Java Beans 更加安全。

猜你喜欢

转载自juejin.im/post/7132349327988490271