建造者模式 Builder

建造者模式

建造者模式(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 对象的各个部件指定的抽象接口,如下的抽象建造者用来确定产品由三个部件 PartAPartBPartC 组成,并声明一个得到产品建造后结果的方法 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;
}

可以看到 CacheBuilderSpringBuilder 一样通过链式调用为其成员赋值,这里有个细节就是成员名就是方法名:

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;
    }

}

看了这么多源码,感觉建造者的角色只保留一个具体建筑者就可以喽。什么指挥者,客户端就是指挥者!

总结

建造者模式将产品本身与产品的创建过程解耦,使得相同的创建过程可以创建不同的产品对象。

工厂模式 是将产品的创建统一到工厂中;如果单个产品创建步骤多,就可以将创建步骤抽象在建造者中。

优点如下:

符合开闭原则:每一个具体建造者都相对独立,而与其他的具体建造者无关,因此可以很方便地替换具体建造者或增加新的具体建造者,用户使用不同的具体建造者即可得到不同的产品对象。

代码清晰:建造者模式拆分了对象的构建过程,将复杂产品的创建步骤分解在不同的方法中,使得创建过程更加清晰。

缺点如下:

和产品关联性太高:产品内部结构如发生变化,所有建造者可能都要做相应的改变。

相关推荐
代码之光_198035 分钟前
保障性住房管理:SpringBoot技术优势分析
java·spring boot·后端
ajsbxi40 分钟前
苍穹外卖学习记录
java·笔记·后端·学习·nginx·spring·servlet
StayInLove1 小时前
G1垃圾回收器日志详解
java·开发语言
对许1 小时前
SLF4J: Failed to load class “org.slf4j.impl.StaticLoggerBinder“
java·log4j
无尽的大道1 小时前
Java字符串深度解析:String的实现、常量池与性能优化
java·开发语言·性能优化
小鑫记得努力1 小时前
Java类和对象(下篇)
java
binishuaio1 小时前
Java 第11天 (git版本控制器基础用法)
java·开发语言·git
zz.YE1 小时前
【Java SE】StringBuffer
java·开发语言
老友@1 小时前
aspose如何获取PPT放映页“切换”的“持续时间”值
java·powerpoint·aspose
wrx繁星点点2 小时前
状态模式(State Pattern)详解
java·开发语言·ui·设计模式·状态模式