建造者模式
建造者模式(Builder),将一个复杂对象的构建与它的表示分离,使得同样的构建过程可以创建不同的表示。
The Builder pattern separates the construction of a complex object from its representation, so that the same construction process can create different representations.
什么是构建 construction
,什么是表示 representation
,建造者模式又怎么将他们分离的,下面看一下建造者模式的 UML
类图。
建造者模式由以下角色组成:
⨳ Product (产品):表示被构建的复杂对象,Builder
模式的目标是创建这个复杂对象。
⨳ Builder(建造者):定义了创建对象各个部分的接口,以及将这些部分组装成最终对象的方法。
⨳ Concrete Builder (具体建造者):实现了 Builder
接口,负责实际构造和装配各个部分。
⨳ Director(指导者):负责调用建造者来创建对象的各个部分,并根据需要组装这些部分来创建对象。
通过 UML
类图,可以看出建造者模式创建的产品是复杂的、由多个部件组成(如上图 Product
有多个成员),而建造者要做的就是通过构建这些部件来创建完整的产品。
抽象建造者(Builder
)定义了构建产品部件的抽象方法,而不同的具体建造者(Concrete Builder
)对构建产品不同的部件,有各自不同的实现。
因为每个具体建造者对构建产品部件有不同的理解,于是指挥者(Director
)只要选择某一个的具体建造者,就能建造出相应的产品。
看到这,应该对构建 construction
和表示representation
有初步认识了。
⨳ 构建 :指的的对象的构建过程,这个构建过程并不是简单的 new
出来,好包括很多属性的初始化;
⨳ 表示:就是由不同构建过程创建出来不同对象。
举个例子,假设有一个汽车对象。在构建汽车的过程中,需要创建引擎、车轮、座位等各个部分,并将它们组装在一起,这个过程就是构建过程 。而表示则是指最终的汽车对象,包括它独特的颜色、型号、速度等属性以及可以执行的方法(比如启动、停止等)。
建造者模式的主要目的就是将构建过程与表示分离开来。这样,即使构建过程变得复杂,我们也可以通过更换具体的建造者来创建不同的表示形式,而不需要修改客户端代码。这提高了系统的灵活性和可维护性。
下面看一下建造者模式的基本实现:
基本实现
产品 Product
产品类,是包含多个组成部件的复杂对象。
java
public class Product {
private String partA;
private String partB;
private String partC;
public void setPartA(String partA) {
this.partA = partA;
}
public void setPartB(String partB) {
this.partB = partB;
}
public void setPartC(String partC) {
this.partC = partC;
}
//显示产品的特性
public void show() {
System.out.println("partA"+partA);
System.out.println("partB"+partB);
System.out.println("partC"+partC);
}
}
抽象建造者 Builder
Builder
是为创建一个 Product
对象的各个部件指定的抽象接口,如下的抽象建造者用来确定产品由三个部件 PartA
、PartB
和 PartC
组成,并声明一个得到产品建造后结果的方法 getResult
。
java
public abstract class Builder {
//创建产品对象
protected Product product = new Product();
public abstract void buildPartA();
public abstract void buildPartB();
public abstract void buildPartC();
//返回产品对象
public Product getResult() {
return product;
}
}
具体建造者 ConcreteBuilder
具体建造者,实现 Builder
接口,构造和装配各个部件。
▪ 具体建造者A
java
public class ConcreteBuilderA extends Builder {
public void buildPartA() {
product.setPartA("建造者A 建造的 PartA");
}
public void buildPartB() {
product.setPartB("建造者A 建造的 PartB");
}
public void buildPartC() {
product.setPartC("建造者A 建造的 PartC");
}
}
▪ 具体建造者B
java
public class ConcreteBuilderB extends Builder {
public void buildPartA() {
product.setPartA("建造者B 建造的 PartA");
}
public void buildPartB() {
product.setPartB("建造者B 建造的 PartB");
}
public void buildPartC() {
product.setPartC("建造者B 建造的 PartC");
}
}
指挥者 Director
指挥者,调用建造者中的方法完成复杂对象的创建。
java
class Director {
private Builder builder;
public Director(Builder builder) {
this.builder = builder;
}
//产品构建与组装方法
public Product construct() {
builder.buildPartA();
builder.buildPartB();
builder.buildPartC();
//builder.getResult()返回的product对象就相当于复杂对象的创建
return builder.getResult();
}
}
客户端 Client
客户端就只需指定需要建造者的类型就可以得到对应的产品,而无需了解具体建造的过程和细节。
java
//创建建造者
Builder builder = new ConcreteBuilderA();
//创建指挥者
Director director = new Director(builder);
//指挥调用建造者方法
Product product = director.construct();
product.show();
System.out.println("**********");
// 更改具体建造者
builder = new ConcreteBuilderB();
director = new Director(builder);
product = director.construct();
product.show();
输出结果如下:
js
partA建造A 建造的 PartA
partB建造A 建造的 PartB
partC建造A 建造的 PartC
**********
partA建造者B 建造的 PartA
partB建造者B 建造的 PartB
partC建造者B 建造的 PartC
建造者模式看起来没啥难度吧,下面看一下其他人都是怎么使用这个模式的。
源码赏析
JDK 之 StringBuilder
StringBuilder
用于动态地构建字符串对象。它提供了一系列方法来对字符串进行修改、追加、插入和删除等操作,而不会创建新的字符串对象,从而节省了内存开销。
如果用建造者模式来理解 StringBuilder
,那 StringBuilder
扮演的角色很多:
⨳ 作为产品,StringBuilder
实例代表了要构建的最终字符串对象。
⨳ 作为抽象建造者:StringBuilder
类定义了一系列方法,用于构建字符串,比如 append()
、insert()
、delete()
等。
⨳ 作为具体建造者:每次调用 append()
、insert()
等方法,都会在现有的字符串基础上进行操作,逐步构建出最终的字符串。
那指挥者呢?没有指挥者,指挥者就是客户端,想要什么样的字符串自己构建就完事了:
java
StringBuilder sb = new StringBuilder();
sb.append("我");
sb.append("爱");
sb.append("黎明");
sb.append("!");
这里需要注意的是 StringBuilder
作为建造者,每次通过 append
方法建造字符串一部分的时候,都将自己作为方法的返回值:
java
@Override
public StringBuilder append(String str) {
super.append(str);
return this;
}
这样就方便客户端进行链式调用了:
java
StringBuilder sb = new StringBuilder();
sb.append("我").append("爱").append("黎明").append("!");
Mybatis 之 BaseBuidler
Mybatis
可能是用 Builder
最多的框架了,全局搜索一下,全是 Builder
,上 UML
类图展示的是继承自 BaseBuidler
的建造者。
BaseBuidler
虽然是个抽象类,但他并不是抽象建造者,这个抽象类只是将子类建造者中通用的代码提取出来形成的。
上 UML
类图展示的四个BaseBuidler
子类,才是真正的建造者,即是抽象建造者又是具体建造者,这四个子类分别建造不同的产品。
⨳ XMLConfigBuilder :通过解析 MyBatis
的配置文件(例如 mybatis-config.xml
),建造Configuration
对象。
⨳ XMLMapperBuilder :负责解析 MyBatis
的映射文件(例如 UserMapper.xml
),并构建对应的 Mapper
接口的映射信息
⨳ XMLStatementBuilder :负责解析 SQL
语句节点,并构建对应的 MappedStatement
对象。
⨳XMLScriptBuilder :负责解析映射文件中的 SQL
节点中的动态 SQL
语句,构建成对应的 SqlSource
对象,用于在执行 SQL
时动态生成 SQL
语句。
可以说 Mybatis
每个建造者都独当一面。就连前文工厂模式 讲的 SqlSessionFactory
都是由 SqlSessionFactoryBuilder
建造出来的。
建造者模式的构建过程在Mybatis
中 ,更多的体现在配置文件中配置项中,由不同的配置项,可以构建出不同的表示。
Mybatis 之 CacheBuidler
Mybatis
的其他 Builder
可能不好借鉴使用,但 CacheBuidler
一定可以借鉴。
CacheBuilder
顾名思义就是为了建造 Cache
缓存而存在的。看一下建造过程的调用:
java
// MapperBuilderAssistant 创建 Cache 方法
public Cache useNewCache(Class<? extends Cache> typeClass, Class<? extends Cache> evictionClass, Long flushInterval,
Integer size, boolean readWrite, boolean blocking, Properties props) {
Cache cache = new CacheBuilder(currentNamespace).implementation(valueOrDefault(typeClass, PerpetualCache.class))
.addDecorator(valueOrDefault(evictionClass, LruCache.class)).clearInterval(flushInterval).size(size)
.readWrite(readWrite).blocking(blocking).properties(props).build();
configuration.addCache(cache);
currentCache = cache;
return cache;
}
可以看到 CacheBuilder
和 SpringBuilder
一样通过链式调用为其成员赋值,这里有个细节就是成员名就是方法名:
java
// CacheBuilder 节选
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;
}
最后 CacheBuilder
调用了 build()
方法完成了 Cache
的创建。
java
// CacheBuilder 节选
public Cache build() {
setDefaultImplementations();
Cache cache = newBaseCacheInstance(implementation, id);
setCacheProperties(cache);
// ...
return cache;
}
CacheBuilder
简单吧,先通过链式调用为其成员属性赋值,在通过 build()
方法,根据成员属性创建不同的 Cache
。
Spring 之 BeanDefinitionBuilder
Spring
框架的 BeanDefinitionBuilder
也是一个建造者,可以构建Bean的定义信息 BeanDefinition
,代码节选如下:
js
public final class BeanDefinitionBuilder {
private final AbstractBeanDefinition beanDefinition;
//构造器BeanDefinitionBuilder创建beanDefinition对象
private BeanDefinitionBuilder(AbstractBeanDefinition beanDefinition) {
this.beanDefinition = beanDefinition;
}
//组装beanDefinition的parentName
public BeanDefinitionBuilder setParentName(String parentName) {
this.beanDefinition.setParentName(parentName);
return this;
}
//组装beanDefinition的factoryMethod
public BeanDefinitionBuilder setFactoryMethod(String factoryMethod) {
this.beanDefinition.setFactoryMethodName(factoryMethod);
return this;
}
//组装beanDefinition的factoryMethod和factoryBean
public BeanDefinitionBuilder setFactoryMethodOnBean(String factoryMethod, String factoryBean) {
this.beanDefinition.setFactoryMethodName(factoryMethod);
this.beanDefinition.setFactoryBeanName(factoryBean);
return this;
}
//组装beanDefinition的ConstructorArgumentValues
public BeanDefinitionBuilder addConstructorArgValue(@Nullable Object value) {
this.beanDefinition.getConstructorArgumentValues().addIndexedArgumentValue(this.constructorArgIndex++, value);
return this;
}
//获取AbstractBeanDefinition 对象
public AbstractBeanDefinition getBeanDefinition() {
this.beanDefinition.validate();
return this.beanDefinition;
}
}
看了这么多源码,感觉建造者的角色只保留一个具体建筑者就可以喽。什么指挥者,客户端就是指挥者!
总结
建造者模式将产品本身与产品的创建过程解耦,使得相同的创建过程可以创建不同的产品对象。
工厂模式 是将产品的创建统一到工厂中;如果单个产品创建步骤多,就可以将创建步骤抽象在建造者中。
优点如下:
⨳ 符合开闭原则:每一个具体建造者都相对独立,而与其他的具体建造者无关,因此可以很方便地替换具体建造者或增加新的具体建造者,用户使用不同的具体建造者即可得到不同的产品对象。
⨳ 代码清晰:建造者模式拆分了对象的构建过程,将复杂产品的创建步骤分解在不同的方法中,使得创建过程更加清晰。
缺点如下:
⨳ 和产品关联性太高:产品内部结构如发生变化,所有建造者可能都要做相应的改变。