写点什么

「 Java 基础 - 链式调用 」Java 开发中如何让你的代码看起来更优雅?试试链式调用?

作者:小刘学编程
  • 2023-02-19
    陕西
  • 本文字数:3625 字

    阅读完需:约 12 分钟

「 Java基础-链式调用 」Java开发中如何让你的代码看起来更优雅?试试链式调用?

一、前言

我们日常在写业务代码的时候,经常会遇到一种场景,比如一个对象有很多属性,比如用户对象有很多属性:用户名、用户 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 通过内部类构建

@Datapublic 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@Builderpublic 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设计模式之建造者模式


感谢前人的经验、分享和付出,让我们可以有机会站在巨人的肩膀上眺望星辰大海!

八、关于作者

感谢阅读此文,公众号搜索「 重载 」"chóng zài" 并关注,不定期分享 Java 相关技术栈资料、后端硬核技术干货、实用笔试面试题。您的每次阅读、每个点赞、每条评论都会激励到博主,博主也会更努力地持续输出优质内容!啊呜(゚▽゚)/ . Talk is cheap (‾◡◝) ! , show me your code ! ヾ(๑╹◡╹)ノ"

发布于: 刚刚阅读数: 4
用户头像

后端工程师@代码都困了💤 2023-02-07 加入

公众号搜索「 重载 」"chóng zài".

评论

发布
暂无评论
「 Java基础-链式调用 」Java开发中如何让你的代码看起来更优雅?试试链式调用?_Java_小刘学编程_InfoQ写作社区