一、主要解决的问题场景
(1)通过将多个简单对象通过⼀步步的组装构建出⼀个复杂
对象的过程,适用前提是物料不变,组合多变的情况。即需要生成的产品
对象有复杂的内部结构,且这些产品对象具有共性。
(2)隔离复杂对象的创建和使用,并使得相同的创建过程可以创建不同的产品(组合多变)
(3)适用于一个具有较多属性的复杂对象的创建过程
(4)创建对象的时候,需要将过多必填字段放入构造方法,而导致构造方法参数列表过长
(5)需要对传入对象的各项参数进行校验,甚至各参数之间还有相互约束关系,那么使用构造方法或者set方法就很难处理这种依赖关系或者约束条件的校验逻辑
二、主要实现方式
建造者模式调用关系 |
---|
![]() |
首先我们需要创建一个抽象的Builder
类,比如CarBuilder
,里面包含的是汽车各个零部件的生产构建过程。
public abstract class CarBuilder {
/**
* 构建引擎
*/
public abstract CarBuilder buildEngine(String engine);
/**
* 构建外壳
*/
public abstract CarBuilder buildShell(String shell);
/**
* 构建电子系统
*/
public abstract CarBuilder buildElecSystem(String elecSystem);
/**
* 构建控制系统
*/
public abstract CarBuilder buildControlSystem(String controlSystem) throws Exception;
/**
* 构建车轮
*/
public abstract CarBuilder buildWheel(String wheel);
/**
* 构建轴承
*/
public abstract CarBuilder buildBearing(String bearing);
/**
* 返回产品:有的地方是将方法名叫“build”,道理一样
*/
public abstract CarProduct getProduct();
}
最终构建出的是一个汽车产品 CarProduct
,我们给定默认值,这样,如果我们的Commander
没有给定参数,就取用默认参数
@Setter
@Getter
public class CarProduct {
/**
* 发动机:默认 V8
*/
private String engine = "V8";
/**
* 外壳:默认 碳纤维
*/
private String shell = "碳纤维";
/**
* 电子系统:默认 AI
*/
private String elecSystem = "AI";
/**
* 控制系统:默认 AutoDrive
*/
private String controlSystem = "AutoDrive";
/**
* 车轮:默认 米其林
*/
private String wheel = "米其林";
/**
* 轴承:默认 冷轧钢板
*/
private String bearing = "冷轧钢板";
/**
* 重写toString,便于打印结果
*/
@Override
public String toString() {
return "CarProduct{" +
"engine='" + engine + '\'' +
", shell='" + shell + '\'' +
", elecSystem='" + elecSystem + '\'' +
", controlSystem='" + controlSystem + '\'' +
", wheel='" + wheel + '\'' +
", bearing='" + bearing + '\'' +
'}';
}
}
我们可以创建多个不同品牌商的Builder
实现类,重写父类方法,同时内部创建产品类,并返回对应的具体产品
public class BugattiBuilder extends CarBuilder{
/**
* 具体建造者实现具体的产品对象
*/
private CarProduct carProduct;
/**
* 无参构造:必须由建造者自己去实例化产品对象
*/
private BugattiBuilder() {
this.carProduct = new CarProduct();
}
/**
* 对外只提供一个静态方法调用创建builder
*/
public static CarBuilder carBuild() {
return new BugattiBuilder();
}
/**
* 以下是对父类方法的,关于汽车构建过程的重写
* 注意:return this,返回这个builder类本身,这样可以链式编成
*/
@Override
public CarBuilder buildEngine(String engine) {
carProduct.setEngine(engine);
return this;
}
@Override
public CarBuilder buildControlSystem(String controlSystem) throws Exception {
//可以单独验参
if (StringUtils.isEmpty(controlSystem)) {
throw new Exception("没有控制系统!");
}
carProduct.setControlSystem(controlSystem);
return this;
}
.......
/**
* 返回具体的产品对象
*/
@Override
public CarProduct getProduct() {
//最后组合返回结果的时候可以将相互依赖或约束的关系进行校验
//比如:如果没有控制系统,那就不建造电子系统
//或者是一些大小的比较,比如ES的builder对象的lt和gt属性操作
if (StringUtils.isEmpty(this.carProduct.getControlSystem())) {
this.carProduct.setElecSystem(null);
}
return carProduct;
}
}
调用关系图中的Commander
,其实是车间主任,或者调用方自己,这样可以很灵活的指导并创建多种不同组合的产品,以满足调用方需求
@RunWith(JUnit4.class)
public class CarBuilderTest {
@Test
public void testCarBuild() {
CarProduct product = BugattiBuilder.carBuild() //创建建造者对象
.buildEngine("V19") //自定义引擎
.buildControlSystem("No-AutoDrive") //自定义控制系统
.buildWheel("国产") //自定义车轮
.getProduct();
System.out.println(product.toString()); //{engine='V19', shell='碳纤维', elecSystem='AI', controlSystem='No-AutoDrive', wheel='国产', bearing='冷轧钢板'}
}
}
三、优缺点
3.1 优点
(1)产品的建造和表示分离(建造的具体过程和最终展示的组合结果),实现了解耦。使用建造者模式可以使客户端不必知道产品内部的组成细节
(2)将复杂的创建步骤分解在不同方法中,使得创建过程更加清晰
(3)具体的建造者类之间是相互独立的,有利于系统扩展,符合开闭原则
3.2 缺点
(1)使用范围受限:必须保证建造的产品有很多的共同点,组成部分类似,如果差异性很大,很多,不要使用
(2)如果产品内部变化复杂,需要定义很多的具体实现的建造者类,导致系统庞大
3.3 与工厂模式的最大区别
着眼点不同,工厂模式着眼于全局,各个品牌簇和各种类型的产品,调用工厂方法返回对应品牌,对应类型产品的结果;而建造者模式着眼于局部,不调用相关方法,而是指导生产,如何生产出满足需求的具体而复杂的产品,返回一个完整对象。