敞开生长之旅!这是我参与「日新计划 2 月更文应战」的第 11 天,点击检查活动概况


一、前语

我们日常在写事务代码的时分,经常会遇到一种场景,比方一个目标有许多特点,比方用户目标有许多特点:用户名、用户ID、用户性别、用户寓居地址、用户作业类型、用户联系方式等等,当我们要构建一个用户目标的时分,就要不断的去set,get如下代码所示:

public class User {
    private String userName;
    private Long userId;
    private String userSex;
    private String userAddress;
    private String userJob;
    private String userPhone;
    private String userBornDate;
}

这种繁琐地set值的代码,会让我们的程序看起来特别臃肿,可读性变差,为了解决这一问题,我们常用的办法一种是创立带参数的结构函数,一种是找个其他类做转换。可是,创立带参数的结构函数时,假如遇到参数太多,这个函数很长看起来很不友爱的情况,并且会遇到我有时分创立需求5个,有时分需求2个参数,那就要求实体类要有多个不同参数的结构函数,要不然就在赋予参数的值的时分,直接就按最长的来,大不了用不到的位置set个null值,可是总归还是很不灵敏。

二、制作者方式(Builder Pattern)

解决上述问题,我们选用一种比较高雅的方式->链式调用:chained invocation(链式调用)或许Method chaining,这种风格的API规划叫做fluent API或许Fluent interface,常用于Builder Pattern(制作者方式)。链式调用的本质就是在办法里边回来目标/其他来完成接连的调用。

2.1 什么是制作者方式?

制作者方式是一种创立型规划方式, 使你能够分进程创立杂乱目标。 该方式答应你运用相同的创立代码生成不同类型和方式的目标。

2.2 制作者方式根本介绍

2.2.1 制作者方式(Builder Pattern) 又名生成器方式,是一种目标构建方式。它能够将杂乱目标的制作进程笼统出来(笼统类别),使这个笼统进程的不同完成办法能够结构出不同体现(特点)的目标。

2.2.2 制作者方式 是一步一步创立一个杂乱的目标,它答应用户只经过指定杂乱目标的类型和内容就能够构建它们,用户不需求知道内部的详细构建细节。

2.3 制作者方式合适应用场景

2.3.1 运用制作者方式可避免 “重叠结构函数 (telescoping constructor)” 的出现。

2.3.2 当你期望运用代码创立不同方式的产品 (例如石头或木头房屋) 时, 可运用制作者方式。

2.3.3 运用制作者结构组合树或其他杂乱目标。

2.4 制作者方式优缺陷

2.4.1 长处

1.能够分步创立目标, 暂缓创立进程或递归运行创立进程。

2.生成不同方式的产品时, 能够复用相同的制作代码。

3.单一职责准则。 能够将杂乱结构代码从产品的事务逻辑中分离出来。

2.4.2 缺陷

因为该方式需求新增多个类,因此代码全体杂乱程度会有所增加。

三、链式调用在java源码中的应用

Java中,最常见的链式调用就是StringBuffer、StringBuilder 类中的 append() 办法。如下所示是StringBuilder类的源代码,篇幅所限,提取了部分代码做示例,实践开发中,我们能够经过接连的.append().append()办法来完成字符串的拼接。如下代码所示:StringBuffer、StringBuilder 这两个类都承继自笼统类 AbstractStringBuilder,该笼统类中也有append() 办法。

public final class StringBuilder
    extends AbstractStringBuilder
    implements java.io.Serializable, CharSequence
{
    // ... 省掉代码 ...
    /**
     * @throws IndexOutOfBoundsException {@inheritDoc}
     */
    @Override
    public StringBuilder append(CharSequence s, int start, int end) {
        super.append(s, start, end);
        return this;
    }
    @Override
    public StringBuilder append(char[] str) {
        super.append(str);
        return this;
    }
    /**
     * @throws IndexOutOfBoundsException {@inheritDoc}
     */
    @Override
    public StringBuilder append(char[] str, int offset, int len) {
        super.append(str, offset, len);
        return this;
    }
    @Override
    public StringBuilder append(boolean b) {
        super.append(b);
        return this;
    }
    // ... 省掉代码 ...
}

四、完成方式

4.1 经过内部类构建

@Data
public class User1 {
    // 真实的特点都是不可变的
    private final int id;
    private final String name;
    private final String job;
    private final String address;
    private final Date birthday;
    // 私有结构办法,只被 Builder 类调用
    private User1(Builder builder) {
        this.id = builder.id;
        this.name = builder.name;
        this.job = builder.job;
        this.address = builder.address;
        this.birthday = builder.birthday;
    }
    public static class Builder {
        // 必须参数
        private int id;
        private String name;
        private Date birthday;
        // 可选参数
        private String job;
        private String address;
        public Builder(int id, String name, Date birthday) {
            this.id = id;
            this.name = name;
            this.birthday = birthday;
        }
        //运用设置好的参数值新建 OperateLog 目标
        public User1 build(){
            return new User1(this);
        }
        // 每个 setter 办法都回来当前的目标,做到链式调用
        public Builder setJob(String job) {
            this.job = job;
            return this;
        }
        public Builder setAddress(String address) {
            this.address = address;
            return this;
        }
    }
}

目标内部类的bulider大概分成四部分:

1、 一个简略的内部类,里边的特点和User特点相同;

2、 内部类的结构函数;

3、 bulid办法,真实核心的一个办法,直接回来一个User实例;

4、 特点的set办法,这一部分都是平行的办法;

客户端类调用实例:

//制作者方式只有在调用build()之后才会创立OperateLog目标。
User1 user1 = new User1.Builder(1,"小明",new Date()).setJob("软件工程师").setAddress("北京").build();

4.2 运用lombok@Builder注解

@Data
@AllArgsConstructor
@NoArgsConstructor
@Builder
public class User2 {
    private String name;
    private String job;
}

4.3 运用lombok@RequiredArgsConstructor@NonNull注解

@Data
@Accessors(chain = true)
@RequiredArgsConstructor(staticName = "of")
public class User3 {
    @NonNull
    private String name;
    private String job;
}

客户端类分别选用上述3种方式构建目标:

public class Client {
    public static void main(String[] args) {
        // 第一种 制作者方式只有在调用build()之后才会创立User1目标
        User1 user1 = new User1.Builder(1,"小明",new Date()).setJob("软件工程师").setAddress("北京").build();
        System.out.println(user1);
        // 第二种
        User2 user2 = User2.builder().name("小明").job("软件工程师").build();
        System.out.println(user2);
        // 第三种
        User3 user3 = User3.of("小明").setJob("软件工程师");
        System.out.println(user3);
    }
}

控制台输出:

User1(id=1, name=小明, job=软件工程师, address=北京, birthday=Sun Feb 19 21:11:12 CST 2023)
User2(name=小明, job=软件工程师)
User3(name=小明, job=软件工程师)

五、什么情况下合适选用这种链式的办法调用?

上述代码演示的链式调用,实践上是同一个目标的多个办法的接连调用。也就是说,在这个长链中的每个办法回来的都是相同的类型、相同的目标,即当前目标自身。例如,StringBuilderappend办法的接连调用,JSONObject中的accumulateput等办法也能够接连调用。这些被调用的办法都有“构建”的特性,都是用于完善实例目标。运用链式调用代码简单编写,看起来比较简练也简单阅读和了解。假如被调用的办法回来的类型不同,则不合适链式调用。因为各办法回来的类型被躲藏了,代码不简单了解,另外在调试的时分也是比较费事的。

六、总结

6.1 长处

编程性强 、可读性强、代码简练。

6.2 缺陷

不太利于代码调试

七、参考 & 道谢

1、学习笔记Java链式调用(办法链)

2、【Java】子类的链式调用

3、java规划方式之制作者方式

感谢前人的经历、分享和付出,让我们能够有时机站在巨人的肩膀上瞭望星斗大海!


敞开生长之旅!这是我参与「日新计划 2 月更文应战」的第 11 天,点击检查活动概况