Effective Java -- 遇到多个构造器参数时要考虑用构建器(Builder)

来源:互联网 发布:淘宝怎么设置子账号 编辑:程序博客网 时间:2024/05/29 16:55

本文是 《Effective Java》的读书笔记,由于是Java进阶书,难免会有理解的偏差,如有错误,非常欢迎能批评指正,本人不胜感激!


什么是构建器(Builder)呢,参加过面试的同学可能会有被问到StringStringBufferStringBuilder的区别,这里就有StringBuilder这个字符串构建器,我们来简单看一下这个构建器的用法吧!

        String sb = new StringBuilder().append("I ")                .append("am ")                .append("student.").toString();

我们看一下这里用的append()函数的源码

    @Override    public StringBuilder append(String str) {        super.append(str);        return this;    }

可知这个append()将传入的str通过调用父类的方法加到了该对象字符串的后面,并将该对象返回了,这样就可以连续的调用append()方法,并且保持对象的唯一性(String每次都会创建一个新的对象)。

在父类方法中使用StringgetChars()方法,将我们传入的str值添加的字符数组value中去。

    public AbstractStringBuilder append(String str) {        if (str == null)            return appendNull();        int len = str.length();        ensureCapacityInternal(count + len);        //将 str 添加到字符数组 value 中去        str.getChars(0, len, value, count);        count += len;        return this;    }

[注]. 如想了解getChars()的工作原理,可以查看String的JDK源码,这里不再说明。

然后我们再来看一下最后的toString()方法

    @Override    public String toString() {        // Create a copy, don't share the array        return new String(value, 0, count);    }

这个方法就会通过value这个字符数组里面的值生成一个String返回过去。


上面我们我们可能对这个Builder有了一个初步的模糊的了解,接下来我就举一个更加容易懂的例子,看一看这个Builder的好处。

设想一个UserInfo类,包含如下字段

属性 说明 是否必要 userId 主键,用户ID 是 userName 用户姓名 是 userPosition 用户职位(Java程序员一定为男性) 否 userSex 性别 否 userAge 年龄 否 userEmail 邮箱 否

可知,在这个类中用户ID,用户姓名是必要的,其他的是非必要的。

在这种参数相对比较多的情况下,使用构造函数创建实例,在客户端的代码就会显得比较麻烦,会分不清构造函数的哪一个参数对应哪一个值。

当然,我们可以为每个参数提供一个setter方法,但是这种方式也有两种不足。
(1)因为构造过程被分到了几次调用中,所以在构造过程中可能会处于不一致的状态。类无法仅仅通过检验构造器参数的有效性来保证一致性。

[注]对于这句话,我个人的理解如下
比如上面的用户类,我们规定如果职位为Java开发工程师,则性别一定是男性。
但是由于setter方法是分成两部进行设置值,而且设置值的顺序也是不一致的,所以就无法使两个参数同时满足上面的要求。

(2)对于一些final类是无法使用setter方法的。


那么有没有一种方法不仅能满足重叠构造器的安全性,又能满足像使用setter那样的易读性呢?

这时候我们的构建器(Builder)就闪耀登场了。使用Builder分成下面的三步:

  • 使用所有的必要字段(如上用户ID,用户姓名)生成一个Builder
  • 在Builder上利用setter方法设置一些非必要的属性(可以一次设置一个或多个,如用户性别和职位的关联,不满足就抛异常)。
  • 调用无参的builder()方法生成不可变的对象。(如StringBuilder中的为toString()方法)。

实例代码如下

package com.blog.effective2;/** * @func 使用构建器(Builder) * Created by 张俊强~ on 2017/10/30. */public class UserInfo {    private final String userId;        //required    private final String userName;      //required    private final String userPosition;    private final String userSex;    private final String userAge;    private final String userEmail;    public static class Builder {        private final String userId;        //required        private final String userName;      //required        private String userPosition = "";        private String userSex = "";        private String userAge = "";        private String userEmail = "";        public Builder setUserEmail(String email) {            userEmail = email;            return this;        }        public Builder setUserSexAndPosition(String sex, String position) {            //出现性别不是[男]的[Java程序员],则抛异常            if ((!"男".equals(sex)) && "Java程序员".equals(position)) {                throw new RuntimeException("参数匹配异常");            }            userSex = sex;            userPosition = position;            return this;        }        public Builder setUserAge(String age) {            userAge = age;            return this;        }        public Builder(String userId, String userName) {            this.userId = userId;            this.userName = userName;        }        //最后的 build 方法        public UserInfo build(){            return new UserInfo(this);        }    }    public UserInfo(Builder builder) {        this.userId=builder.userId;        this.userName=builder.userName;        this.userPosition=builder.userPosition;        this.userAge = builder.userAge;        this.userEmail = builder.userEmail;        this.userSex = builder.userSex;    }    @Override    public String toString() {        return "UserInfo{" +                "userId='" + userId + '\'' +                ", userName='" + userName + '\'' +                ", userPosition='" + userPosition + '\'' +                ", userSex='" + userSex + '\'' +                ", userAge='" + userAge + '\'' +                ", userEmail='" + userEmail + '\'' +                '}';    }}

有如下的测试代码

    public static void main(String[] args) {        // 格式与 StringBuilder 是一致的.        UserInfo userInfo=new UserInfo.Builder("001","张三").setUserAge("23")                .setUserEmail("test@test.com")                .setUserSexAndPosition("男","Java程序员").build();        System.out.println(userInfo.toString());    }    //输出结果    //UserInfo{userId='001', userName='张三', userPosition='Java程序员', userSex='男', userAge='23', userEmail='test@test.com'}

在这种模式下,由于在创建对象的同时需要创建相应的Builder对象,产生额外的花销。

简而言之,如果类的有多个参数时,可以考虑使用Builder模式(在该类中有很多参数是不必要的情况下,Builder模式表现的就更好)。


2017/10/30 13:05 于 福州大学.

阅读全文
0 0
原创粉丝点击