3. 建造者设计模式
3.1 原理
Builder 模式,中文翻译为建造者模式
或者构建者模式
,也有人叫它生成器模式
。
建造者模式是用来创建一种类型的复杂对象,通过设置不同的可选参数,"定制化"地创建不同的对象。
创建者模式主要包含以下四个角色:
- 产品(Product):表示将要被构建的复杂对象。
- 抽象创建者(Abstract Builder):定义构建产品的接口,通常包含创建和获取
产品的方法。 - 具体创建者(Concrete Builder):实现抽象创建者定义的接口,为产品的各个
部分提供具体实现。 - 指挥者(Director):负责调用具体创建者来构建产品的各个部分,控制构建过
程。
我们以文档编辑器
为例,假设我们需要创建一个复杂的HTML文档,它包含了标题、段落和图像等元素。我们可以使用创建者设计模式来构建HTML文档。
产品(Product)类 - HTML文档(HtmlDocument):
java
/**
* 类描述:产品(Product),表示将要被构建的复杂对象。
* 需求:创建一个复杂的HTML文档,它包含了标题、段落和图像等元素。我们可以使用创建者设计模式来构建HTML文档。
*
* @Author crysw
* @Version 1.0
* @Date 2023/11/21 22:57
*/
public class HtmlDocument {
private String header;
private String body;
private String footer;
public void addHeader(String header) {
this.header = header;
}
public void addBody(String body) {
this.body = body;
}
public void addFooter(String footer) {
this.footer = footer;
}
@Override
public String toString() {
return "<html><head>" + header + "</head><body>" + body + "</body><footer>"
+ footer + "</footer></html>";
}
}
抽象创建者(Abstract Builder)类 - HtmlDocumentBuilder:
java
/**
* 类描述:抽象创建者(Abstract Builder),定义构建产品的接口,通常包含创建和获取产品的方法。
*
* @Author crysw
* @Version 1.0
* @Date 2023/11/21 23:01
*/
public abstract class HtmlDocumentBuilder {
/**
* 文档对象
*/
protected HtmlDocument document;
/**
* 获取document文档对象
*
* @return
*/
public HtmlDocument getDocument() {
return document;
}
/**
* 创建document文档对象
*/
public void createNewHtmlDocument() {
document = new HtmlDocument();
}
/**
* 构建文档对象的header
*/
public abstract void buildHeader();
/**
* 构建文档对象的body
*/
public abstract void buildBody();
/**
* 构建文档对象的footer
*/
public abstract void buildFooter();
}
具体创建者(Concrete Builder)类 - ArticleHtmlDocumentBuilder:
java
/**
* 类描述:具体创建者(Concrete Builder),实现抽象创建者定义的接口,为产品的各个部分提供具体实现。
*
* @Author crysw
* @Version 1.0
* @Date 2023/11/21 23:04
*/
public class ArticleHtmlDocumentBuilder extends HtmlDocumentBuilder {
@Override
public void buildHeader() {
document.addHeader("Article Header");
}
@Override
public void buildBody() {
document.addBody("Article Body");
}
@Override
public void buildFooter() {
document.addFooter("Article Footer");
}
}
指挥者(Director)类 - HtmlDirector:
java
public class HtmlDirector {
/**
* 构建者
*/
private HtmlDocumentBuilder builder;
public HtmlDirector(HtmlDocumentBuilder builder) {
this.builder = builder;
}
/**
* 构建文档对象
*/
public void constructDocument() {
builder.createNewHtmlDocument();
builder.buildHeader();
builder.buildBody();
builder.buildFooter();
}
/**
* 获取document对象
*
* @return
*/
public HtmlDocument getDocument() {
return builder.getDocument();
}
}
现在测试使用创建者设计模式来构建一个HTML文档对象:
java
/**
* 类描述:构建者设计模式的测试
*
* @Author crysw
* @Version 1.0
* @Date 2023/11/21 23:16
*/
@Slf4j
public class BuilderPatternTest {
@Test
public void testHtmlDocumentBuilder() {
HtmlDocumentBuilder htmlDocumentBuilder = new ArticleHtmlDocumentBuilder();
HtmlDirector director = new HtmlDirector(htmlDocumentBuilder);
director.constructDocument();
HtmlDocument htmlDocument = director.getDocument();
log.info(">>> Constructed HTML Document: \n {}", htmlDocument.toString());
}
}
在这个例子中,我们创建了一个表示HTML文档的产品类(HtmlDocument),一个抽象的创建者类(HtmlDocumentBuilder),一个具体的创建者类(ArticleHtmlDocumentBuilder)和一个指挥者类(HtmlDirector)。当我们需要创建一个新的HTML文档对象时,我们可以使用指挥者类
来控制构建过程,从而实现了将构建过程与表示过程的分离。
以上是一个创建者设计模式的标准写法,为了创建一个对象,我们创建了很多辅助的类,总觉得不太合适。在工作中往往不会写的这么复杂,我们可以使用内部类来简化代码,以下是修改后的代码(甚至我们还移除了抽象层):
java
/**
* 类描述:产品(Product),表示将要被构建的复杂对象。
* 需求:创建一个复杂的HTML文档,它包含了标题、段落和图像等元素。我们可以使用创建者设计模式来构建HTML文档。
*
* @Author crysw
* @Version 1.0
* @Date 2023/11/21 22:57
*/
public class HtmlDocument {
private String header;
private String body;
private String footer;
public void addHeader(String header) {
this.header = header;
}
public void addBody(String body) {
this.body = body;
}
public void addFooter(String footer) {
this.footer = footer;
}
@Override
public String toString() {
return "<html><head>" + header + "</head><body>" + body + "</body><footer>"
+ footer + "</footer></html>";
}
public static Builder builder() {
return new Builder();
}
public static class Builder {
protected HtmlDocument document;
private Builder() {
document = new HtmlDocument();
}
public Builder addHeader(String header) {
document.addHeader(header);
return this;
}
public Builder addBody(String body) {
document.addBody(body);
return this;
}
public Builder addFooter(String footer) {
document.addFooter(footer);
return this;
}
public HtmlDocument build() {
return document;
}
}
}
再来测试创建一个HTML文档对象:
java
/**
* 类描述:构建者设计模式的测试
*
* @Author crysw
* @Version 1.0
* @Date 2023/11/21 23:16
*/
@Slf4j
public class BuilderPatternTest {
@Test
public void testHtmlDocumentBuilder2() {
HtmlDocument htmlDocument = HtmlDocument.builder().addHeader("This is the header")
.addBody("This is body")
.addFooter("This is footer")
.build();
log.info(">>> Constructed HTML Document: \n {}", htmlDocument.toString());
}
}
3.2 为什么需要建造者模式
(1) 根据复杂的配置项进行定制化构建。
先看一个mybaits中经典的案例,这个案例中使用了装饰器和创建者设计模式:
java
public class CacheBuilder {
private final String id;
private Class<? extends Cache> implementation;
private final List<Class<? extends Cache>> decorators;
private Integer size;
private Long clearInterval;
private boolean readWrite;
private Properties properties;
private boolean blocking;
public CacheBuilder(String id) {
this.id = id;
this.decorators = new ArrayList<Class<? extends Cache>>();
}
public CacheBuilder implementation(Class<? extends Cache> implementation) {
this.implementation = implementation;
return this;
}
public CacheBuilder addDecorator(Class<? extends Cache> decorator) {
if (decorator != null) {
this.decorators.add(decorator);
}
return this;
}
public CacheBuilder size(Integer size) {
this.size = size;
return this;
}
public CacheBuilder clearInterval(Long clearInterval) {
this.clearInterval = clearInterval;
return this;
}
public CacheBuilder readWrite(boolean readWrite) {
this.readWrite = readWrite;
return this;
}
public CacheBuilder blocking(boolean blocking) {
this.blocking = blocking;
return this;
}
public CacheBuilder properties(Properties properties) {
this.properties = properties;
return this;
}
public Cache build() {
setDefaultImplementations();
Cache cache = newBaseCacheInstance(implementation, id);
setCacheProperties(cache);
// issue #352, do not apply decorators to custom caches
// 根据配置的装饰器对原有缓存进行增强,如增加淘汰策略等
if (PerpetualCache.class.equals(cache.getClass())) {
for (Class<? extends Cache> decorator : decorators) {
cache = newCacheDecoratorInstance(decorator, cache);
setCacheProperties(cache);
}
cache = setStandardDecorators(cache);
} else if (!LoggingCache.class.isAssignableFrom(cache.getClass())) {
cache = new LoggingCache(cache);
}
return cache;
}
}
我们总结这个案例中的几个特点:
- 参数有必填项id,有很多可选填的内容,通常必选项id通过构造器传入,可选项通过方法传递。
- 真正的构建过程需要调用build方法,构建时需要根据已配置的成员变量的内容选择合适的装饰器,对目标cache进行增强。
(2) 实现不可变对象
创建者设计模式(Builder Design Pattern)可以实现不可变对象,即一旦创建完成,对象的状态就不能改变。这有助于保证对象的线程安全和数据完整性。下面是一个使用创建者设计模式实现的不可变对象的Java示例:
java
/**
* 类描述:实现不可变对象
*
* @Author crysw
* @Version 1.0
* @Date 2023/11/26 21:21
*/
public final class ImmutablePerson {
private final String name;
private final int age;
private final String address;
private ImmutablePerson(Builder builder) {
this.name = builder.name;
this.age = builder.age;
this.address = builder.address;
}
public String getName() {
return name;
}
public int getAge() {
return age;
}
public String getAddress() {
return address;
}
public Builder builder() {
return new Builder();
}
public static class Builder {
private String name;
private int age;
private String address;
public Builder() {
}
public Builder setName(String name) {
this.name = name;
return this;
}
public Builder setAge(int age) {
this.age = age;
return this;
}
public Builder setAddress(String address) {
this.address = address;
return this;
}
public ImmutablePerson build() {
return new ImmutablePerson(this);
}
}
}
在这个例子中, ImmutablePerson 类具有三个属性: name 、age 和 address 。这些属性都是 final 修饰,一旦设置就不能更改。ImmutablePerson 的构造函数是私有的且没有set方法,外部无法直接创建该类的实例。需要使用内部的 Builder 类要创建一个 ImmutablePerson 实例,通过连续调用 Builder 类的方法,来为ImmutablePerson 设置属性。最后调用 build() 方法,创建一个具有指定属性的不可变 ImmutablePerson 实例。
3.3 源码应用
创建者设计模式在源码中有广泛的使用:
(1)jdk中的StringBuilder和StringBuffer,他们的实现不是完全按照标准的创建者设计模式设计,但也是一样的思想。
这两个类用于构建和修改字符串。它们实现了创建者模式,允许通过方法链来修改字符串。这些类在性能上优于 String 类,因为它们允许在同一个对象上执行多次修改,而不需要每次修改都创建一个新的对象。
java
StringBuilder builder = new StringBuilder();
builder.append("Hello").append(" ").append("World!");
String result = builder.toString();
(2)在ssm源码中使用创建者设计模式,如Spring中的BeanDefinitionBuilder 类,mybatis中SqlSessionFactoryBuilder 、
XMLConfigBuilder 、XMLMapperBuilder 、XMLStatementBuilder 、CacheBuilder 等。
(3)使用lombok实现创建者设计模式
Lombok 是一个 Java 库,它可以简化代码,提高开发效率,尤其是在实现模式和生成常用方法(例如 getter、setter、equals、hashCode 和 toString)时。以下是一个使用 Lombok 的创建者设计模式的例子:
首先在项目中引入了 Lombok 依赖:
xml
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<version>1.18.12</version>
<scope>provided</scope>
</dependency>
创建一个类, 使用lombok的相关注解实现建造者模式:
java
/**
* 类描述:使用Lombok构建实例,查看编译后的class文件,会自动生成构建对象的代码(使用的构建者模式)
*
* @Author crysw
* @Version 1.0
* @Date 2023/11/20 23:19
*/
@NoArgsConstructor
@AllArgsConstructor
@Data
@Builder
public class Person {
private String name;
private Integer age;
}
反编译后的Person类:
java
package cn.itcast.designPatterns.builder;
public class Person {
private String name;
private Integer age;
public static PersonBuilder builder() {
return new PersonBuilder();
}
public Person() {
}
public Person(String name, Integer age) {
this.name = name;
this.age = age;
}
public String getName() {
return this.name;
}
public Integer getAge() {
return this.age;
}
public void setName(String name) {
this.name = name;
}
public void setAge(Integer age) {
this.age = age;
}
public boolean equals(Object o) {
if (o == this) {
return true;
} else if (!(o instanceof Person)) {
return false;
} else {
Person other = (Person)o;
if (!other.canEqual(this)) {
return false;
} else {
Object this$name = this.getName();
Object other$name = other.getName();
if (this$name == null) {
if (other$name != null) {
return false;
}
} else if (!this$name.equals(other$name)) {
return false;
}
Object this$age = this.getAge();
Object other$age = other.getAge();
if (this$age == null) {
if (other$age != null) {
return false;
}
} else if (!this$age.equals(other$age)) {
return false;
}
return true;
}
}
}
protected boolean canEqual(Object other) {
return other instanceof Person;
}
public int hashCode() {
int PRIME = true;
int result = 1;
Object $name = this.getName();
result = result * 59 + ($name == null ? 43 : $name.hashCode());
Object $age = this.getAge();
result = result * 59 + ($age == null ? 43 : $age.hashCode());
return result;
}
public String toString() {
return "Person(name=" + this.getName() + ", age=" + this.getAge() + ")";
}
// 内部类建造者
public static class PersonBuilder {
private String name;
private Integer age;
PersonBuilder() {
}
public PersonBuilder name(String name) {
this.name = name;
return this;
}
public PersonBuilder age(Integer age) {
this.age = age;
return this;
}
public Person build() {
return new Person(this.name, this.age);
}
public String toString() {
return "Person.PersonBuilder(name=" + this.name + ", age=" + this.age + ")";
}
}
}
测试lombok创建的建造者模式实现的类
java
@Test
public void testPerson() {
Person person = builder().name("crysw")
.age(23)
.build();
person.setName("panda");
person.setAge(22);
log.info(">>> person: {}", person);
}