IntelliJ中自动生成构建器类
1. 简介
Builder 设计模式 是使用最广泛的创建模式之一。它帮助我们构建复杂的对象。
手动编写构建器既麻烦又容易出错。因此,我们应该尽可能使用专用工具自动生成它们。
在本教程中,我们将探索在IntelliJ IDE 中自动创建构建器类的不同方法。我们将了解 IntelliJ 提供的开箱即用的内置功能以及第三方插件。
2. 初始设置
在本文中,我们将使用 IntelliJ IDEA 社区版的 2019.1.3 版本,这是撰写本文时的最新版本。但是,示例中介绍的所有技术也应该适用于任何其他版本的 IDEA。
让我们从定义我们将为其生成构建器的Book类开始:
public class Book {
private String title;
private Author author;
private LocalDate publishDate;
private int pageCount;
// standard constructor(s), getters and setters
}
3. 使用 IntelliJ 的内置功能
要使用 IntelliJ 的内置工具为Book类生成构建器,我们需要一个合适的构造器。
让我们创建一个:
public Book(String title, Author author, LocalDate publishDate, int pageCount) {
this.title = title;
this.author = author;
this.publishDate = publishDate;
this.pageCount = pageCount;
}
现在,我们准备创建一个构建器。因此,让我们将光标放在创建的构造函数上,然后按Ctrl+Alt+Shift+T
(在 PC 上)打开Refactor This
弹出窗口,然后选择Replace Constructor with Builder
refactoring:
我们可以进一步调整构建器类的一些选项,比如它的名称和目标包:
结果,我们生成了BookBuilder类:
public class BookBuilder {
private String title;
private Author author;
private LocalDate publishDate;
private int pageCount;
public BookBuilder setTitle(String title) {
this.title = title;
return this;
}
public BookBuilder setAuthor(Author author) {
this.author = author;
return this;
}
public BookBuilder setPublishDate(LocalDate publishDate) {
this.publishDate = publishDate;
return this;
}
public BookBuilder setPageCount(int pageCount) {
this.pageCount = pageCount;
return this;
}
public Book createBook() {
return new Book(title, author, publishDate, pageCount);
}
}
3.1.自定义设置器前缀
在构建器类中为 setter 方法使用with前缀是一种常见的做法。
要更改默认前缀,我们需要选择选项窗口右上角的Rename Setters Prefix图标:
3.2. 静态内部生成器
我们中的一些人可能更喜欢将构建器实现为静态内部类,如 Joshua Bloch 在 Effective Java 中所述。
如果是这种情况,我们需要采取一些额外的步骤来使用 IntelliJ 的Replace Constructor with Builder功能来实现这一点。
首先,我们需要手动创建一个空的内部类,并将构造函数设为私有:
public class Book {
private String title;
private Author author;
private LocalDate publishDate;
private int pageCount;
public static class Builder {
}
private Book(String title, Author author, LocalDate publishDate, int pageCount) {
this.title = title;
this.author = author;
this.publishDate = publishDate;
this.pageCount = pageCount;
}
// standard getters and setters
}
此外,我们必须在选项窗口中选择Use existing并指向我们新创建的类:
4. 使用 InnerBuilder 插件
现在让我们看看如何使用InnerBuilder 插件为Book类生成构建器。
安装插件后,我们可以通过按Alt+Insert(在 PC 上)并选择*Builder…*选项来打开Generate 弹出窗口:
或者,我们可以通过按Alt+Shift+B(在 PC 上)直接调用 InnerBuilder 插件:
如我们所见,我们可以选择一些选项来自定义生成的构建器。
让我们看看所有选项都未选中时生成的构建器:
public static final class Builder {
private String title;
private Author author;
private LocalDate publishDate;
private int pageCount;
public Builder() {
}
public Builder title(String val) {
title = val;
return this;
}
public Builder author(Author val) {
author = val;
return this;
}
public Builder publishDate(LocalDate val) {
publishDate = val;
return this;
}
public Builder pageCount(int val) {
pageCount = val;
return this;
}
public Book build() {
return new Book(this);
}
}
默认情况下,InnerBuilder 插件将构建器实现为静态内部类。
5. 使用生成器生成器插件
最后,让我们看看Builder Generator 是如何工作的。
同样,对于 InnerBuilder,我们可以按Alt+Insert(在 PC 上)并选择Builder选项或使用Alt+Shift+B
快捷键。
正如我们所看到的,我们可以从三个选项中选择自定义BookBuilder:
让我们不选中所有选项并查看生成的构建器类:
public final class BookBuilder {
private String title;
private Author author;
private LocalDate publishDate;
private int pageCount;
private BookBuilder() {
}
public static BookBuilder aBook() {
return new BookBuilder();
}
public BookBuilder withTitle(String title) {
this.title = title;
return this;
}
public BookBuilder withAuthor(Author author) {
this.author = author;
return this;
}
public BookBuilder withPublishDate(LocalDate publishDate) {
this.publishDate = publishDate;
return this;
}
public BookBuilder withPageCount(int pageCount) {
this.pageCount = pageCount;
return this;
}
public Book build() {
Book book = new Book();
book.setTitle(title);
book.setAuthor(author);
book.setPublishDate(publishDate);
book.setPageCount(pageCount);
return book;
}
}
Builder Generator 插件提供的第一个选项来自定义创建的构建器类 - 内部构建器 - 是不言自明的。
然而,另外两个更有趣,我们将在以下部分中探讨它们。
5.1. but方法选项
如果我们选择此选项,插件将向BookBuilder类添加一个*but()*方法:
public BookBuilder but() {
return aBook().withTitle(title).withAuthor(author)
.withPublishDate(publishDate).withPageCount(pageCount);
}
现在,假设我们要创建三本书,作者相同,页数相同,但标题和出版日期不同。我们可以创建一个已经设置了公共属性的基础构建器,然后使用but()方法从中创建新的BookBuilder(以及稍后的Book)。
我们来看一个例子:
BookBuilder commonBuilder = BookBuilder.aBook().withAuthor(johnDoe).withPageCount(123);
Book my_first_book = commonBuilder.but()
.withPublishDate(LocalDate.of(2017, 12, 1))
.withTitle("My First Book").build();
Book my_second_book = commonBuilder.but()
.withPublishDate(LocalDate.of(2018, 12, 1))
.withTitle("My Second Book").build();
Book my_last_book = commonBuilder.but()
.withPublishDate(LocalDate.of(2019, 12, 1))
.withTitle("My Last Book").build();
5.2. 使用单个字段选项
如果我们选择此选项,生成的构建器将保存对创建的Book对象的引用,而不是所有书籍的属性:
public final class BookBuilder {
private Book book;
private BookBuilder() {
book = new Book();
}
public static BookBuilder aBook() {
return new BookBuilder();
}
public BookBuilder withTitle(String title) {
book.setTitle(title);
return this;
}
public BookBuilder withAuthor(Author author) {
book.setAuthor(author);
return this;
}
public BookBuilder withPublishDate(LocalDate publishDate) {
book.setPublishDate(publishDate);
return this;
}
public BookBuilder withPageCount(int pageCount) {
book.setPageCount(pageCount);
return this;
}
public Book build() {
return book;
}
}
这是创建在某些情况下可能派上用场的构建器类的有点不同的方法。