笔者的碎碎念
其实之前有写过建造者模式的文章,但是感觉其实写的不怎么样,而且自己也理解的一般,但是阅读一些框架源码发现,这些模式真的蛮重要的,很多框架例如OkHttp,Retrofit等等都大量使用了建造者模式以及一些其他的设计模式,于是决定对这些模式进行学习和整理,将写一个专栏来记录自己的的学习记录,你知道的,好记性不如烂笔头,加油!
建造者模式的由来
因 Java 中没有命名参数的概念,当一个类的构造器可选参数太多的时候,代码可读性会变得很差。我们通过一个例子来说明,假设我们有一个连接池的配置类 ConnectionPoolConfig
,它包含了多个可选参数,比如 maxConnections
(最大连接数)、minConnections
(最小连接数)、timeout
(超时时间)等。为了支持不同的配置选项,最初可能会使用伸缩式构造器模式或者JavaBeans构造器模式来创建这个对象。
伸缩式构造器模式
public class ConnectionPoolConfig {private int maxConnections;private int minConnections;private int timeout;public ConnectionPoolConfig(int maxConnections, int minConnections, int timeout) {this.maxConnections = maxConnections;this.minConnections = minConnections;this.timeout = timeout;}
...省略ConnectionPoolConfig中的其他构造方法// Getters and setters
}
使用伸缩式构造器模式,我们可能会遇到以下问题:
-
参数顺序依赖性: 如果某些参数是可选的,并且它们的顺序与构造函数中的参数顺序不匹配,那么我们就不得不在构造对象时填充未使用的默认值,比如:
ConnectionPoolConfig config = new ConnectionPoolConfig(10, 5, 0); // timeout 默认为 0
-
参数类型相似性: 如果两个参数类型相似(比如都是整数),在构造对象时容易搞错参数的顺序,这可能会导致严重的错误。
JavaBeans构造器模式
然后,我们针对这些进行改进,有了后来的JavaBeans构造器模式。
public class ConnectionPoolConfig {private int maxConnections;private int minConnections;private int timeout;public ConnectionPoolConfig() {// Empty constructor}// Setterspublic void setMaxConnections(int maxConnections) {this.maxConnections = maxConnections;}public void setMinConnections(int minConnections) {this.minConnections = minConnections;}public void setTimeout(int timeout) {this.timeout = timeout;}
...省略// Getters
}
使用JavaBeans构造器模式,虽然解决了参数顺序依赖性的问题,但引入了新的问题:
- 对象状态不一致性: 构建对象需要多次调用不同的 setter 方法,这可能会导致对象在构造过程中处于不一致的状态。例如,如果某个字段在设置之前被访问,可能得到不完整或不正确的对象状态。
建造者模式
于是,建造者模式(Builder Pattern)应运而生。
建造者模式在这些问题的基础上提供了更加灵活和安全的对象构建方式:
public class ConnectionPoolConfig {private final int maxConnections;private final int minConnections;private final int timeout;private ConnectionPoolConfig(Builder builder) {this.maxConnections = builder.maxConnections;this.minConnections = builder.minConnections;this.timeout = builder.timeout;}// Getterspublic static class Builder {private int maxConnections;private int minConnections;private int timeout;public Builder() {// 默认值或者空构造器}public Builder maxConnections(int maxConnections) {this.maxConnections = maxConnections;return this;}public Builder minConnections(int minConnections) {this.minConnections = minConnections;return this;}public Builder timeout(int timeout) {this.timeout = timeout;return this;}public ConnectionPoolConfig build() {return new ConnectionPoolConfig(this);}}
}
优点:
- 链式调用: 使用建造者模式,可以使用链式调用来设置对象的各个属性,清晰地表达出构建对象的步骤和顺序。
ConnectionPoolConfig config = new ConnectionPoolConfig.Builder().maxConnections(10).minConnections(5).timeout(0).build();
- 对象不可变性: 在建造者模式中,可以将对象设计为不可变的(Immutable),一旦构建完成后,对象的状态不可修改,保证了对象的线程安全性和一致性。
- 消除对象状态不一致性问题: 建造者模式通过在最终构建之前保持对象状态的一致性,避免了JavaBeans模式中可能出现的对象状态不一致性问题。
- 易于解耦:将产品本身与产品创建过程进行解耦,可以使用相同的创建过程来得到不同的产品。也就说细节依赖抽象。
- 易于精确控制对象的创建:将复杂产品的创建步骤分解在不同的方法中,使得创建过程更加清晰
- 易于拓展:增加新的具体建造者无需修改原有类库的代码,易于拓展,符合“开闭原则“。
每一个具体建造者都相对独立,而与其他的具体建造者无关,因此可以很方便地替换具体建造者或增加新的具体建造者,用户使用不同的具体建造者即可得到不同的产品对象。
模式原理
使用场景
建造者模式适用于需要创建复杂对象(对象有多个部分,且构建过程复杂)的场景,或者需要创建多个相似对象(只有部分属性不同)的场景。它有效地解决了伸缩式构造器模式和JavaBeans构造器模式存在的问题,并提供了一种更加优雅和灵活的解决方案。你经常能在Android看到一些常见的技术框架中都使用了该模式,例如OkHttp框架中就有它的大量使用。关于OkHttp我也有文章进行分析,如果你感兴趣,可以去看看OkHttp中是如何使用建造者模式的->【传送门】。
使用
-
构造者的创建:客户端通过创建一个具体的建造者对象(如
Builder
),并使用链式调用来设置产品的各个属性
ConnectionPoolConfig config = new ConnectionPoolConfig.Builder().maxConnections(10).minConnections(5).timeout(30).build();
-
属性设置:
每次调用建造者的设置方法(如maxConnections
、minConnections
、timeout
)时,建造者内部会更新自己的状态,以便在构建最终产品时使用。 -
构建产品:
最终调用build()
方法时,建造者将使用其内部状态来实例化并初始化产品对象ConnectionPoolConfig
,并返回给客户端。