共计 8458 个字符,预计需要花费 22 分钟才能阅读完成。
疯狂的类结构器
最近栈长在做 Code Review 时,发现一段创建对象的办法:
Task task = new Task(112, "紧急任务", "解决一下这个工作", 90, 3, 1, 36, "刘主管", 18, "客服 1", "11, 12, 13", "客服 3, 客服 4, 客服 5", true, new Date(), new Date(), new Date(), new Date(), new Date(), 0, "须要尽快实现", ...);
实在代码敏感性,下面的代码仅为模拟,理论要比这个更长、更简单……
当我看到那段代码时,我几乎要疯了!!
拖了半天才看完,到处充斥着魔法值不说,把一个类所有参数都放在一个结构器外面,这个结构器也太疯狂了……这种写法也切实太 low 了!
在理论开发过程中,栈长常常看到共事们这样的写法,比下面的更长的结构器你见过没?我反正见过!
一方面,兴许他们真不知道怎么写才更好,毕竟教训无限,这个能够原谅。但另一方面,兴许他们就是为了偷懒,或者为了赶时间,反正这都是对本人和共事不负责的体现。
如果你在公司看到共事写这样的优良代码,请把这篇文章发给他。
看看大量参数结构器的毛病:
- 参数过多时,代码太长,极不优雅,保护极难;
- 不能判断出哪些是必须参数,哪些是可选参数,可选参数也得给个默认值;
- 分不清变量值对应哪个变量,如程序对应错,很容易造成谬误;
- 结构器参数增减时,会影响所有创立该对象的中央,影响扩展性;
结构器正确的用法是只给出几个必选、重要参数的结构器,而不是把所有参数放在一个结构器中。
比方咱们看下 JDK 线程池的结构器用法:
线程池就把几个重要的参数封装成了几个结构器,这样用户就能够依据理论须要调用具体的某个结构器。
根本 SET 办法改进
再回到共事写的那个代码,写出那样长的结构器,我真的服了。
最根本也得写成这样吧:
Task task = new Task();
task.setId(112);
task.setName("紧急任务");
task.setContent("解决一下这个工作");
task.setParentId(90);
task.setType(3);
task.setLevel(1);
task.setAssignFromId(36);
task.setAssignFromName("刘主管");
task.setAssignTo(18);
task.setAssignTo("客服 1");
task.setCandidateId("11, 12, 13");
task.setCandidateName("客服 3, 客服 4, 客服 5");
task.isSendEmail(true);
task.setCreateTime(new Date());
task.setBeginTime(new Date());
task.setEndTime(new Date());
task.setFinishTime(new Date());
task.setUpdateTime(new Date());
task.setStatus(0);
task.setMemo("须要尽快实现");
// ...
这个创建对象的形式是最一般不过了,也是用的最多的了。
这种写法尽管看起来很直观,然而有以下几个毛病:
- 参数多的状况下 setter 十分多,代码十分长,不是很优雅;
- 不能判断出哪些是必须参数,哪些是可选参数;
- 容易漏掉一些参数,并且很难查看进去;
- 容易搞错对象名,造成潜在谬误,很难排查(如:同时有 user 和 user2,在 user 赋值过程中谬误的复制了 user2 对象);
Builder 模式改进
上面栈长教大家用 Builder 模式改进下,上面是改进后的代码:
package cn.javastack.test.designpattern.builder;
import java.util.Date;
/**
* @author: 栈长
* @from: 公众号 Java 技术栈
*/
public class Task {
private long id;
private String name;
private String content;
private int type;
private int status;
private Date finishDate;
private Task(TaskBuilder taskBuilder) {
this.id = taskBuilder.id;
this.name = taskBuilder.name;
this.content = taskBuilder.content;
this.type = taskBuilder.type;
this.status = taskBuilder.status;
this.finishDate = taskBuilder.finishDate;
}
/**
* @author: 栈长
* @from: 公众号 Java 技术栈
*/
public static class TaskBuilder {
private long id;
private String name;
private String content;
private int type;
private int status;
private Date finishDate;
public TaskBuilder(long id, String name) {
this.id = id;
this.name = name;
}
public TaskBuilder content(String content) {
this.content = content;
return this;
}
public TaskBuilder type(int type) {
this.type = type;
return this;
}
public TaskBuilder status(int status) {
this.status = status;
return this;
}
public TaskBuilder finishDate(Date finishDate) {
this.finishDate = finishDate;
return this;
}
public Task build(){return new Task(this);
}
}
public long getId() {return id;}
public String getName() {return name;}
public String getContent() {return content;}
public int getType() {return type;}
public int getStatus() {return status;}
public Date getFinishDate() {return finishDate;}
@Override
public String toString() {
return "Task{" +
"id=" + id +
", name='" + name + '\'' +
", content='" + content + '\'' +
", type=" + type +
", status=" + status +
", finishDate=" + finishDate +
'}';
}
}
说下简略思路:
1)在 Bean 类外面新建一个动态外部类:XxxBuilder;
2)把 Bean 类所有参数复制到 XxxBuilder,而后在 XxxBuilder 新建必须参数的结构器,其余参数应用变量名作为办法而后返回本身(this)以便造成链式调用;
3)在 Bean 类外面新建一个接管 XxxBuilder 参数的公有结构器,防止应用 new 创建对象;
4)在 XxxBuilder 类新建一个 build 办法开始构建 Bean 类,也是作为链式调用的完结;
应用办法:
应用形式如下,先创立结构器,而后在每个办法后应用 . 带出所有办法,高深莫测,最初调用 build 办法以完结链式调用创立 bean。
参考代码如下:
/**
* @author: 栈长
* @from: 公众号 Java 技术栈
*/
private static void testBuilder() {Task task = new Task.TaskBuilder(99, "紧急任务")
.type(1)
.content("解决一下这个工作")
.status(0)
.finishDate(new Date())
.build();
System.out.println(task);
}
后果输入:
Task{id=99, name=’ 紧急任务 ’, content=’ 解决一下这个工作 ’, type=1, status=0, finishDate=…
Builder 模式的长处:
- 链式调用,优雅、清晰、高深莫测;
- 一行代码实现对象创立,防止了多行代码赋值过程出错;
- 省去了大量冗余变量,防止变量复制出错;
Builder 模式的毛病:
- 须要冗余的 Builder 类,以及大量相等反复的成员变量,大大增加了代码量,保护难度绝对较大;
- 只适宜一次赋值创建对象,屡次赋值的场景还须要新增 set 办法配合,不是很灵便;
Lombok 实现 Builder 模式
惯例的 Builder 模式须要新增大量的代码,保护难度比拟大,这里栈长再介绍一下 Lombok 中的 Builder 模式,一个 @Builder 注解搞定所有,轻松保护。
用 Lombok 改进后的代码如下:
/**
* @author: 栈长
* @from: 公众号 Java 技术栈
*/
@Builder
public class LombokTask {
private long id;
private String name;
private String content;
private int type;
private int status;
private Date finishDate;
}
我还能说什么?两个字:真香!
再来看下怎么应用:
/**
* @author: 栈长
* @from: 公众号 Java 技术栈
*/
private static void testLombokBuilder() {LombokTask lombokTask = new LombokTask.LombokTaskBuilder()
.id(99)
.name("紧急任务")
.type(1)
.content("解决一下这个工作")
.status(0)
.finishDate(new Date())
.build();
System.out.println(lombokTask);
}
或者 new 都不要了,间接调用静态方法:
/**
* @author: 栈长
* @from: 公众号 Java 技术栈
*/
private static void testLombokBuilder2() {LombokTask lombokTask = LombokTask.builder()
.id(99)
.name("紧急任务")
.type(1)
.content("解决一下这个工作")
.status(0)
.finishDate(new Date())
.build();
System.out.println(lombokTask);
}
接下来咱们来看下这个 @Builder 注解到底做了什么:
public class LombokTask {
private long id;
private String name;
private String content;
private int type;
private int status;
private Date finishDate;
LombokTask(final long id, final String name, final String content, final int type, final int status, final Date finishDate) {
this.id = id;
this.name = name;
this.content = content;
this.type = type;
this.status = status;
this.finishDate = finishDate;
}
public static LombokTask.LombokTaskBuilder builder() {return new LombokTask.LombokTaskBuilder();
}
public static class LombokTaskBuilder {
private long id;
private String name;
private String content;
private int type;
private int status;
private Date finishDate;
LombokTaskBuilder() {}
public LombokTask.LombokTaskBuilder id(final long id) {
this.id = id;
return this;
}
public LombokTask.LombokTaskBuilder name(final String name) {
this.name = name;
return this;
}
public LombokTask.LombokTaskBuilder content(final String content) {
this.content = content;
return this;
}
public LombokTask.LombokTaskBuilder type(final int type) {
this.type = type;
return this;
}
public LombokTask.LombokTaskBuilder status(final int status) {
this.status = status;
return this;
}
public LombokTask.LombokTaskBuilder finishDate(final Date finishDate) {
this.finishDate = finishDate;
return this;
}
public LombokTask build() {return new LombokTask(this.id, this.name, this.content, this.type, this.status, this.finishDate);
}
public String toString() {return "LombokTask.LombokTaskBuilder(id=" + this.id + ", name=" + this.name + ", content=" + this.content + ", type=" + this.type + ", status=" + this.status + ", finishDate=" + this.finishDate + ")";
}
}
}
这是反编译后的代码,能够看进去逻辑都是一样的。
Lombok 还能够增加各种类结构器、toString 等系列注解,几个注解齐全能够达到想要的成果,但代码量和可维护性是天壤之别。
很多人不倡议应用 Lombok,仁者见仁,智者见智,这里不再探讨,相干话题能够浏览我之前写的文章:
- 举荐一款代码神器,代码量至多省一半!
- 公司来了个新共事不会用 Lombok,还说我代码有问题!
应用 Lombok 带来了很多便当,不必多说,是真的香,货色是好货色,就是要团队标准一起应用,防止踩坑。更多工具系列应用文章请关注公众号 Java 技术栈,在菜单中浏览。
Java 8 实现 Builder 模式
Java 8 带来了函数式接口编程,所以在 Java 8 中能够一个实现通用的 Builder:
public class GenericBuilder<T> {
private final Supplier<T> instantiator;
private List<Consumer<T>> instanceModifiers = new ArrayList<>();
public GenericBuilder(Supplier<T> instantiator) {this.instantiator = instantiator;}
public static <T> GenericBuilder<T> of(Supplier<T> instantiator) {return new GenericBuilder<T>(instantiator);
}
public <U> GenericBuilder<T> with(BiConsumer<T, U> consumer, U value) {Consumer<T> c = instance -> consumer.accept(instance, value);
instanceModifiers.add(c);
return this;
}
public T build() {T value = instantiator.get();
instanceModifiers.forEach(modifier -> modifier.accept(value));
instanceModifiers.clear();
return value;
}
}
参考:
http://www.ciphermagic.cn/jav…
应用形式:
/**
* @author: 栈长
* @from: 公众号 Java 技术栈
*/
private static void testJava8Builder() {Java8Task java8Task = GenericBuilder.of(Java8Task::new)
.with(Java8Task::setId, 99L)
.with(Java8Task::setName, "紧急任务")
.with(Java8Task::setType, 1)
.with(Java8Task::setContent, "解决一下这个工作")
.with(Java8Task::setStatus, 0)
.with(Java8Task::setFinishDate, new Date())
.build();
System.out.println(java8Task);
}
这样一来,任何带有默认结构器和 set 办法的类都能够应用这个通用的 Builder 模式了。
尽管利用 Java 8 是实现了通用有 Builder 模式,但还是有很多冗余的代码,而且实质还是调用的 set 办法,所以和 set 比起来只是多了一个链式调用而已。
Spring Boot 中的 Builder 模式
Spring Boot 是当初支流的利用框架,其中也用到了 Builder 模式,可见 Builder 模式的常见性。
上面再来看下 Spring Boot 是怎么利用 Builder 模式的:
new SpringApplicationBuilder()
.sources(Parent.class)
.child(Application.class)
.bannerMode(Banner.Mode.OFF)
.run(args);
如上代码所示,这是 Spring Boot 的链式启动形式。
Spring Boot 基础教程看这里:
https://github.com/javastacks…
咱们来看它是怎么做的:
它是新增了一个 XxxBuilder 类:SpringApplicationBuilder,而后在 SpringApplicationBuilder 中新增了个 SpringApplication 的成员变量,而后再新增变量对应的办法。
所以,Spring Boot 只是用 SpringApplicationBuilder 包装了一下 SpringApplication 而已,写法有所不同,但中心思想都是一样的。这里就不再演示了,大家也能够了借鉴一下。
总结
本文说了共事写的疯狂的类结构器,而后再介绍了用 set 办法改进,以及应用 4 种 Builder 模式改进的形式,上面来总结一下吧:
- 惯例的 Builder 模式
- Lombok 实现 Builder 模式(举荐)
- Java 8 实现 Builder 模式
- Spring Boot 中的 Builder 模式
如果团队有应用 Lombok,那么 Lombok 无疑是最佳举荐的形式,代码量少,应用简略,不便保护。其余三种实现形式都各有利弊,大家都能够参考应用。
总之,别再写疯狂的类结构器了……
如果你在公司看到共事写这样的优良代码,请把这篇文章发给他。
好了,明天的分享就到这里了,前面栈长我会更新更多 Java 技术实战及设计模式系列文章,公众号 Java 技术栈第一工夫推送。
本节教程所有实战源码已上传到这个仓库:
https://github.com/javastacks…
最初,感觉我的文章对你用播种的话,动动小手,给个在看、转发,原创不易,栈长须要你的激励。
版权申明:本文系公众号 “Java 技术栈 ” 原创,原创实属不易,转载、援用本文内容请注明出处,禁止剽窃、洗稿,请自重,尊重别人劳动成果和知识产权。
近期热文举荐:
1.600+ 道 Java 面试题及答案整顿 (2021 最新版)
2. 终于靠开源我的项目弄到 IntelliJ IDEA 激活码了,真香!
3. 阿里 Mock 工具正式开源,干掉市面上所有 Mock 工具!
4.Spring Cloud 2020.0.0 正式公布,全新颠覆性版本!
5.《Java 开发手册(嵩山版)》最新公布,速速下载!
感觉不错,别忘了顺手点赞 + 转发哦!