建造者模式|构建者模式|生成器模式
实际上,建造者模式的原理和代码实现非常简单,掌握起来并不难,难点在于应用场景。
比如,你有没有考虑过这样几个问题:直接使用构造函数或者配合 set 方法就能创建对象,为什么还需要建造者模式来创建呢?
建造者模式和工厂模式都可以创建对象,那它们两个的区别在哪里呢?
当创建一个对象是,构造函数参数很多,并且参数之间有逻辑关系。此时构造函数列表列表变的很长,代码在可读性和易用性上变差。在使用构造函数的时候,容易搞错各参数的顺序。传递进去错误的参数值,导致非常隐蔽的bug
// 参数太多,导致可读性差、参数可能传递错误
ResourcePoolConfig config = new ResourcePoolConfig("dbconnectionpool", 16, null, 8, null, false , true, 10, 20,false, true);
解决这个问题的办法你应该也已经想到了,那就是用 set() 函数来给成员变量赋值,以替代冗长的构造函数。
// ResourcePoolConfig使用举例
ResourcePoolConfig config = new ResourcePoolConfig("dbconnectionpool");
config.setMaxTotal(16);
config.setMaxIdle(8);
- 我们刚刚讲到,name 是必填的,所以,我们把它放到构造函数中,强制创建对象的时候就设置。如果必填的配置项有很多,把这些必填配置项都放到构造函数中设置,那构造函数就又会出现参数列表很长的问题。如果我们把必填项也通过 set() 方法设置,那校验这些必填项是否已经填写的逻辑就无处安放了。
- 除此之外,假设配置项之间有一定的依赖关系,比如,如果用户设置了 maxTotal、maxIdle、minIdle 其中一个,就必须显式地设置另外两个;或者配置项之间有一定的约束条件,比如,maxIdle 和 minIdle 要小于等于 maxTotal。如果我们继续使用现在的设计思路,那这些配置项之间的依赖关系或者约束条件的校验逻辑就无处安放了。
- 如果我们希望 ResourcePoolConfig 类对象是不可变对象,也就是说,对象在创建好之后,就不能再修改内部的属性值。要实现这个功能,我们就不能在 ResourcePoolConfig 类中暴露 set() 方法。
使用场景总结:
基于上面三点需求:
1.有的属性是必传的,而且必传属性还挺多。。。
2.属性直接有依赖关系。。。
3.希望对象创建好以后不可变,也就是不对外暴露set()方法
此时就需要使用建造者模式了--实现步骤
1.校验逻辑放在Builder类中 ,先创建建造者。并且通过set()方法设置建造者的变量值。
2.然后在使用build()方法真正创建对象之前,做集中校验。校验通过之后才能创建对象。
3.除此之外,我们可以把ResourcePoolConfig的构造函数改为private私有权限。这样就只能通过创建者来创建ResourcePoolConfig类对象。
4.并且ResourcePoolConfig不提供任何set()方法。这样我们创建出出来的对象就是不可变的了
代码如下
package leven.texous.testmail.designPattern.create.builder;
public class ResourcePoolConfig {
private String name;
private int maxTotal ;
private int maxIdle ;
private int minIdle ;
/**
* 问题1:必填字段如果太多,都写在构造函数里,会越来越大
* 问题2:必填字段如果不写在构造函数里,用setter方法,则校验工作无从进行
* 问题3:如果字段之间有关联关--校验逻辑也无处安放
* 问题4:如果我们希望该类一旦创建是不可变对象,也就是创建好以后不能修改内部属性值。那么就不能再类中暴露set()方法
* <p>
* ---------------基于以上,建造者模式就派上用场了---------------
* 先集中校验,校验通过才去创建对象。
* 把构造函数设置为私有权限,--这样我们就只能通过建造者来创建对象,并且没有提供set()方法,这样创建的对象就是不可变的
* <p>
* <p>
* 问题抛出:采用先创建后set()模式,在第一个set()之后,对象处于无效状态
* <p>
* 解决:为了避免上述问题,需要使用构造函数,一次性初始化好所有的成员变量
* <p>
* 如果变量太多则考虑建造者模式,如果不关注
*
* @param name
*/
public ResourcePoolConfig(String name) {//name必填,直接要求写在构造函数中
this.name = name;
}
private ResourcePoolConfig(Builder builder) {//构造者模式
this.name = builder.name;
this.maxIdle = builder.maxIdle;
this.minIdle = builder.minIdle;
this.maxTotal = builder.maxTotal;
}
public static class Builder {
private static final int DEFAULT_MAX_TOTAL = 8;
private static final int DEFAULT_MAX_IDLE = 8;
private static final int DEFAULT_MIN_IDLE = 0;
private String name;
private int maxTotal = DEFAULT_MAX_TOTAL;
private int maxIdle = DEFAULT_MAX_IDLE;
private int minIdle = DEFAULT_MIN_IDLE;
/**
*
* 实际上,使用建造者模式创建对象,还能避免对象存在无效状态
*
* build方法中 返回new的对象
* @return
*/
public ResourcePoolConfig build() {
// if (StringUtils.isBlank(name)) {
if (true) {
throw new IllegalArgumentException("...");
}
if (maxIdle > maxTotal) {
throw new IllegalArgumentException("...");
}
if (minIdle > maxTotal || minIdle > maxIdle) {
throw new IllegalArgumentException("...");
}
return new ResourcePoolConfig(this);
}
public Builder setName(String name) {
// if (StringUtils.isBlank(name)) {
if (true) {
throw new IllegalArgumentException("...");
}
this.name = name;
return this;
}
public Builder setMaxTotal(int maxTotal) {
if (maxTotal <= 0) {
throw new IllegalArgumentException("...");
}
this.maxTotal = maxTotal;
return this;
}
public Builder setMaxIdle(int maxIdle) {
if (maxIdle < 0) {
throw new IllegalArgumentException("...");
}
this.maxIdle = maxIdle;
return this;
}
public Builder setMinIdle(int minIdle) {
if (minIdle < 0) {
throw new IllegalArgumentException("...");
}
this.minIdle = minIdle;
return this;
}
}
public static void main(String[] args) {
// 这段代码会抛出IllegalArgumentException,因为minIdle>maxIdle
ResourcePoolConfig config = new ResourcePoolConfig.Builder()
.setName("dbconnectionpool")
.setMaxTotal(16)
.setMaxIdle(10)
.setMinIdle(12).build();
}
}
与工厂模式的区别:
工厂模式是用来创建不同但是相关类型的对象(继承同一父类|实现同一接口的一组子类)由给定的参数来决定创建哪种类型的对象。
建造者模式是用用来创建一种 类型的复杂对象。通过设置不同参数,”定制化“地创建不同的对象
网上有一个经典的例子很好地解释了两者的区别。
顾客走进一家餐馆点餐,
我们利用工厂模式,根据用户不同的选择,来制作不同的食物,比如披萨、汉堡、沙拉。
对于披萨来说,用户又有各种配料可以定制,比如奶酪、西红柿、起司,我们通过建造者模式根据用户选择的不同配料来制作披萨。