一、前言
我们日常在写业务代码的时候,经常会遇到一种场景,比如一个对象有很多属性,比如用户对象有很多属性:用户名、用户ID、用户性别、用户居住地址、用户工作类型、用户联系方式等等,当我们要构建一个用户对象的时候,就要不断的去set,get
如下代码所示:
publicclassUser { privateStringuserName; privateLonguserId; privateStringuserSex; privateStringuserAddress; privateStringuserJob; privateStringuserPhone; privateStringuserBornDate; }
这种繁琐
地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()
方法。
publicfinalclassStringBuilderextendsAbstractStringBuilderimplementsjava.io.Serializable, CharSequence{ // ... 省略代码 .../*** @throws IndexOutOfBoundsException {@inheritDoc}*/publicStringBuilderappend(CharSequences, intstart, intend) { super.append(s, start, end); returnthis; } publicStringBuilderappend(char[] str) { super.append(str); returnthis; } /*** @throws IndexOutOfBoundsException {@inheritDoc}*/publicStringBuilderappend(char[] str, intoffset, intlen) { super.append(str, offset, len); returnthis; } publicStringBuilderappend(booleanb) { super.append(b); returnthis; } // ... 省略代码 ...}
四、实现方式
4.1 通过内部类构建
publicclassUser1 { // 真正的属性都是不可变的privatefinalintid; privatefinalStringname; privatefinalStringjob; privatefinalStringaddress; privatefinalDatebirthday; // 私有构造方法,只被 Builder 类调用privateUser1(Builderbuilder) { this.id=builder.id; this.name=builder.name; this.job=builder.job; this.address=builder.address; this.birthday=builder.birthday; } publicstaticclassBuilder { // 必须参数privateintid; privateStringname; privateDatebirthday; // 可选参数privateStringjob; privateStringaddress; publicBuilder(intid, Stringname, Datebirthday) { this.id=id; this.name=name; this.birthday=birthday; } //使用设置好的参数值新建 OperateLog 对象publicUser1build(){ returnnewUser1(this); } // 每个 setter 方法都返回当前的对象,做到链式调用publicBuildersetJob(Stringjob) { this.job=job; returnthis; } publicBuildersetAddress(Stringaddress) { this.address=address; returnthis; } } }
对象内部类的bulider
大概分成四部分:
1、 一个简单的内部类,里面的属性和
User
属性相同;2、 内部类的构造函数;3、bulid
方法,真正核心的一个方法,直接返回一个User
实例;4、 属性的set
方法,这一部分都是平行的方法;
客户端类调用实例:
//建造者模式只有在调用build()之后才会创建OperateLog对象。User1user1=newUser1.Builder(1,"小明",newDate()).setJob("软件工程师").setAddress("北京").build();
4.2 使用lombok
的@Builder
注解
publicclassUser2 { privateStringname; privateStringjob; }
4.3 使用lombok
的@RequiredArgsConstructor
和@NonNull
注解
chain=true) (staticName="of") (publicclassUser3 { privateStringname; privateStringjob; }
客户端类分别采用上述3种方式构建对象:
publicclassClient { publicstaticvoidmain(String[] args) { // 第一种 建造者模式只有在调用build()之后才会创建User1对象User1user1=newUser1.Builder(1,"小明",newDate()).setJob("软件工程师").setAddress("北京").build(); System.out.println(user1); // 第二种User2user2=User2.builder().name("小明").job("软件工程师").build(); System.out.println(user2); // 第三种User3user3=User3.of("小明").setJob("软件工程师"); System.out.println(user3); } }
控制台输出:
User1(id=1, name=小明, job=软件工程师, address=北京, birthday=SunFeb1921:11:12CST2023) User2(name=小明, job=软件工程师) User3(name=小明, job=软件工程师)
五、什么情况下适合采用这种链式的方法调用?
上述代码演示的链式调用,实际上是同一个对象的多个方法的连续调用。也就是说,在这个长链中的每个方法返回的都是相同的类型、相同的对象,即当前对象本身。例如,StringBuilder
中append
方法的连续调用,JSONObject
中的accumulate
、put
等方法也可以连续调用。这些被调用的方法都有“构建”的特性,都是用于完善实例对象。使用链式调用代码容易编写,看起来比较简洁也容易阅读和理解。如果被调用的方法返回的类型不同,则不适合链式调用。
因为各方法返回的类型被隐藏了,代码不容易理解,另外在调试的时候也是比较麻烦的。
六、总结
6.1 优点
编程性强 、可读性强、代码简洁。
6.2 缺点
不太利于代码调试
七、参考 & 鸣谢
感谢前人的经验、分享和付出,让我们可以有机会站在巨人的肩膀上眺望星辰大海!