Java:Effective java学习笔记之 遇到多个构造器参数时要考虑用构建器

构建器

1、构建一个对象的几种方式

  • 1、多构造函数:这种方式虽然最方便,但也最繁琐,假设有一个方法需要设置6个int型的参数,但多数情况下,这6个中总有你不想传的参数,这时候就需要约定一个规则:例如传0,但是传0又会有一个问题就是传入顺序问题,传入顺序不同并不会导致程序编译不通过,却会导致程序运行结果不正确。所以这并不是一种好方式
  • 2、setter:虽然它可以解决多构造带来的传入顺序问题,但是它还是略显繁琐了。所以我们通常只会在JavaBean中使用setter
  • 3、Builder模式:本文要介绍的一种方式,可以完美解决上述两种方式带来的弊端,让代码更加符合设计模式。参考Glide和Retrofit的使用方式,它们都是通过链式调用一个一个参数连接起来的

多构造器举例

public class UserConstruct {
    
    

    /**
     * id
     */
    private Long id;

    /**
     * 姓名
     */
    private String name;

    /**
     * 身份证号
     */
    private String idNumber;

    /**
     * 地址
     */
    private String address;

    /**
     * 信息是否完整
     * true : name,idNumber,address必须都有的情况下
     * false : 上述属性有一个或多个为空
     */
    private boolean infoIsComplete;

    public UserConstruct(Long id, String name) {
    
    
        this(id, name, null, null);
    }

    public UserConstruct(Long id, String name, String idNumber) {
    
    
        this(id, name, idNumber, null);
    }

    public UserConstruct(Long id, String name, String idNumber, String address) {
    
    
        this.id = id;
        this.name = name;
        this.idNumber = idNumber;
        this.address = address;
        if (null != id && null != name 
                && null != idNumber && null != address) {
    
    
            this.infoIsComplete = true;
        }
    }
}

例子中的UserConstruct.java类共有5个成员变量,其中id,name,infoIsComplete是必选成员变量且infoIsComplete不是通过用户设置的,而是根据对象实际的属性设值情况进行赋值的。而idNumber,address则是两个可选变量。

当我们需要创建一个拥有地址而没有身份证号码的对象时,我们会这么写:

UserConstruct user = new UserConstruct(1L, "张三", null, "上海市");

但是构造器调用通常会需要很多你根本不想要设置的参数,但是又不得不为它们传值。
重叠构造器模式可行,但是当有许多参数的时候,客户端代码会很难编写,并且仍然较难以阅读。

当有一连串相同类型的参数的时候,会很容易颠倒其中一些参数的顺序,而编译器却不会出错。

所以会通常会想到用JavaBean模式来替代。

sette:JavaBean的方式

在此模式下,我们通常都是通过调用一个无参构造器来构建对象,然后通过调用setter方法来设置每个必要参数,以及每个相关的可选参数。我们的用户类变成了这样:

public class UserJavaBean {
    
    

    /**
     * id
     */
    private Long id;

    /**
     * 姓名
     */
    private String name;

    /**
     * 身份证号
     */
    private String idNumber;

    /**
     * 地址
     */
    private String address;

    /**
     * 信息是否完整
     * true : name,idNumber,address必须都有的情况下
     * false : 上述属性有一个或多个为空
     */
    private boolean infoIsComplete;


    public void setId(Long id) {
    
    
        this.id = id;
    }

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

    public void setIdNumber(String idNumber) {
    
    
        this.idNumber = idNumber;
    }

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

    public void setInfoIsComplete(boolean infoIsComplete) {
    
    
        this.infoIsComplete = infoIsComplete;
    }
}

那么在JavaBean模式下我们初始化一个重叠构造器中例子就变成了下面这样:

扫描二维码关注公众号,回复: 13241001 查看本文章
UserJavaBean user = new UserJavaBean();
user.setId(1L);
user.setName("张三");
user.setAddress("上海市");
user.setInfoIsComplete(false);

这样确实代码的可读性变强了,但是也有那么几个缺点:

  • 无法保证必要参数

这种方式我们没办法保证所有的必要参数都如我们所愿地被赋上了值,当然了这个缺点还有弥补的方案,就是我们通过调用一个包含所有必要参数的构造器来获取一个对象,而只通过setter方法来设置相关的可选参数。

  • 构造过程中JavaBean可能会处于不一致的状态

对于这一点书中的描述有点晦涩难懂,不知道是否是翻译的问题。一个例子是infoIsComplete这个字段必须在所有的属性都被赋值后才为true,这样通过JavaBean的方式我们也没有很好的方式来通过代码的方式自动为其赋值。

还有一点就是,如果idNumber和address两个两个可选参数必须同时存在的时候,使用JavaBean也是有点力不从心了。

  • JavaBean模式阻止了把类变成不可变的可能

一旦我们使用了JavaBean模式,则表明我们会为类的每个属性都编写一个共有的setter方法,也就说明我们的类无法成为一个不可变类。

2、定义和使用场景

Builder的定义是:将一个复杂对象的构建与它的表示分离,使得同样的构建过程可以创建不同的表示

使用场景

主要强调两点:调用顺序、多参数

  • 1、相同的方法,不同的执行顺序,需要产生不同的事件结果时

  • 2、多个部件或零件,都可以装配到一个对象中,但是产生的运行结果又不相同的时候

  • 3、产品类非常复杂,或者产品类中的调用顺序不同产生了不同的作用时

  • 4、当初始化一个对象特别复杂,如参数多,且很多参数都具有默认值时

3、优势和不足

构建器有下面几个优点:

  • 可以和构造器一样对参数强加约束条件,比如idNumber和address必须同时存在。
  • 可以拥有多个可变参数
  • 十分灵活,可以通过单个Builder构建多个对象,也可在构建对象时对参数进行调整。

当然了,构建器也有不足的地方:

  • 为了创建一个对象,我们首先必须创建一个它的构建器对象,这可能在一定程度上会消耗我们的内存。所以在一些比较注重性能的情况下构建器就不那么好使了。

但是在极大多数的情况下,建议我们在还是在当前或者未来参数数量比较大的类中使用构建器。

3、使用

根据书中的描述,构建器既能保证像重叠构造器那样的安全性,也可以有JavaBean模式那样的可读性。

public class UserBuilder {
    
    

    /**
     * id
     */
    private Long id;

    /**
     * 姓名
     */
    private String name;

    /**
     * 身份证号
     */
    private String idNumber;

    /**
     * 地址
     */
    private String address;

    /**
     * 信息是否完整
     * true : name,idNumber,address必须都有的情况下
     * false : 上述属性有一个或多个为空
     */
    private boolean infoIsComplete;

    /**
     * 构建器
     */
    public static class Builder {
    
    
        // 必传参数
        private Long id;
        private String name;
        // 可选参数
        private String idNumber;
        private String address;

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

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

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

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


    private UserBuilder(Builder builder) {
    
    
        this.id = builder.id;
        this.name = builder.name;
        this.idNumber = builder.idNumber;
        this.address = builder.address;
        // 根据参数赋值情况,给infoIsComplete赋值
        if (this.id != null && this.name!= null
                && this.idNumber!= null && this.address != null) {
    
    
            this.infoIsComplete = true;
        }
    }
}

还是上面的例子,我们就可以改写为:

UserBuilder user = new Builder(1L, "张三").address("上海市").build();

是不是同样的易读,而且我们还可以在UserBuilder的构造器中进行参数的验证,并且可以顺利的给infoIsComplete属性自动赋值。

参考

1、《Effective Java》第2条:遇到多个构造器参数时要考虑用构建器
2、《Effective java》读书记录-第2条-遇到多个构造器参数时要考虑用构建器
3、【读书笔记】《Effective Java》第二章 第2条:遇到多个构造器参数时要考虑使用Builder

猜你喜欢

转载自blog.csdn.net/JMW1407/article/details/121397607