建造者模式是日常开发中比较常见的设计模式,它的主要作用就是将复杂事物创建的过程抽象出来,该抽象的不同实现方式不同,创建出的对象也不同。通俗的讲,创建一个对象一般都会有一个固定的步骤,这个固定的步骤我们把它抽象出来,每个抽象步骤都会有不同的实现方式,不同的实现方式创建出的对象也将不同。举个常见的例子,想必大家都买过车,车的生产或者组装其实就是属于建造者模式,我们知道,车的生产都需要安装发动机,变速箱,底盘等组件。我们可以把这个安装步骤抽象出来,至于到底装哪种发动机,比如宝马还是奔驰就是对该抽象安装步骤的具体实现。
设计模式 |
---|
![]() |
模式思想
现在我们好好想一想,我们能从客户订车这个小故事中能学到什么呢?首先对于客户来说,不论他订购哪种型号的车,他只需告诉车厂所订购的车型就好了;其次对于车厂而言,他不论制造什么型号的车,生产线的流程基本是固定的,比如这里可以是先装底盘、再装发动机、最后装轮胎。即构建过程是一致的,唯一区别的就是在制造不同车型的生产流水线上,所使用的零部件型号不同。这样就可以实现同样的生产线流程可以制造出不同型号的汽车
至此我们就从中提取到了一种思想,在软件开发领域我们称之为Builder Pattern建造者模式。在建造者模式中有4个角色:
- Product : 产品,即最终被建造出的对象。就像上文所言的那样车厂最终是要生产出产品(即汽车)给客户的。同样地在软件开发中,作为一种创建型的设计模式,它的最终目的也是为了创建出一个对象
- Director : 导演/指挥者。这个角色的目的是指挥建造者完成一个固定的建造流程。就像车厂里的生产流水线一样,无论哪种型号的车其所需经过工序都是一致的,流水线负责推动整个建造作业的流程顺序
- Builder、ConcreteBuilder : 抽象建造者、具体建造者。车厂里三种车型都需要工人们来装底盘、装发动机、装变速箱,这些统一的行为就可以通过抽象建造者来约定,具体到软件开发中就是通过接口或抽象类来实现;而同一个行为在不同的生产线上会因为使用不同型号的零部件而产生差异,对应到软件开发中就是会有若干个具体建造者来实现成员变量参数的差异组合
实践
先来创建一个Car的Product的类
/**
* 车
*/
@Data //lombok生成setter,getter方法
@ToString //lombok生成tostring方法
@NoArgsConstructor //lombok生成无参构造方法
public class Car {
/**
* 底盘
*/
private String chassis;
/**
* 发动机
*/
private String engine;
/**
* 变速箱
*/
private String gearbox;
}
我们可以看到Car这个类包含了3个成员变量:底盘、发动机、变速箱,所以对于Builder抽象建造者来说,其目标就是定义设置各个成员变量的行为,以及建造完成后获取产品的行为 getCar()。这里我们是通过接口来进行定义的
/**
* 车抽象建造者的接口
* @date 2022年03月21日 12:33
*/
public interface CarConfigBuilder {
/**
* 创建底盘
*/
void createChassis();
/**
* 创建发动机
*/
void createEngine();
/**
* 创建变速箱
*/
void createGearbox();
/**
* 返回整车
*
* @return
*/
Car getCar();
}
现在就需要通过具体的建造者ConcreteBuilder来实现不同类型的车建造,这里我们提供了2个具体建造者,用于建造2种不同类型的车
/**
* 奔驰车的建造
*/
public class BenzBuilder implements CarConfigBuilder{
private Car car;
public BenzBuilder() {
this.car = new Car();
}
/**
* 创建底盘
*/
@Override
public void createChassis() {
car.setChassis("安装奔驰底盘~~~");
}
/**
* 创建发动机
*/
@Override
public void createEngine() {
car.setEngine("安装奔驰6缸发动机!!");
}
/**
* 创建变速箱
*/
@Override
public void createGearbox() {
car.setGearbox("安装奔驰变速箱!!!");
}
/**
* 返回整车
*
* @return
*/
@Override
public Car getCar() {
return car;
}
}
/**
* 宝马车的建造
*/
public class BmwBuilder implements CarConfigBuilder{
private Car car;
public BmwBuilder() {
this.car = new Car();
}
/**
* 创建底盘
*/
@Override
public void createChassis() {
car.setChassis("安装宝马底盘~~~");
}
/**
* 创建发动机
*/
@Override
public void createEngine() {
car.setEngine("安装宝马6缸发动机!!");
}
/**
* 创建变速箱
*/
@Override
public void createGearbox() {
car.setGearbox("安装宝马变速箱!!!");
}
/**
* 返回整车
*
* @return
*/
@Override
public Car getCar() {
return car;
}
}
至此,我们就差一个流水线——Director,来推动、指挥具体的建造者依次完成各个建造工序
/**
* 导演: 负责指定车的建造流程
*/
public class Director {
private CarConfigBuilder configBuilder;
public void setBuilder(CarConfigBuilder builder){
this.configBuilder = builder;
}
public void createCar(){
configBuilder.createChassis();
configBuilder.createEngine();
configBuilder.createGearbox();
}
public Car getCar(){
return configBuilder.getCar();
}
现在,我们就完成了一个基于Builder Pattern的轿车建造了。现在让我们一起来看看怎么订购轿车吧,我们首先需要构造一个导演实例——director,然后构造一个期望订购的车的建造者。当我们把这个建造者交给导演时,就"自动"完成了车的建造,我们这些客户再也不需要Care构造车过程中成员变量的参数设置了。最后,当我们从建造者手中取出已经建造好的车就可以了。至此,我们就完成了一个轿车的对象实例的构造过程
/**
* Builder Pattern 建造者模式 Demo
*/
public class Client {
public static void main(String[] args) {
Director director = new Director();
System.out.println("------组装宝马车------");
director.setBuilder(new BmwBuilder());
director.createCar();
Car bmwCar = director.getCar();
System.out.println(bmwCar.toString());
System.out.println("------组装奔驰车------");
director.setBuilder(new BenzBuilder());
director.createCar();
Car benzCar = director.getCar();
System.out.println(benzCar.toString());
}
}
测试结果 |
---|
![]() |
简易版建造者模式
上文介绍的建造者模式,更多的是适用于子部分组合结果一定的场景。而对于子部分组合情况多样的场景就使用不方便了。这样在构造对象实例时,要么通过构造器一次性进行初始化,要么不断地调用setXxxx方法进行初始化。当其成员变量较多时,就会显得代码十分繁琐冗长、不够优雅。为了解决这一问题,简化版 Builder Pattern 应运而生。这里我们同样以Car为例通过使用简化版 Builder Pattern 重写该类
/**
* 车
*/
@Data //lombok生成setter,getter方法
@ToString //lombok生成tostring方法
@NoArgsConstructor //lombok生成无参构造方法
public class Car {
/**
* 底盘
*/
private String chassis;
/**
* 发动机
*/
private String engine;
/**
* 变速箱
*/
private String gearbox;
public Car(String chassis, String engine, String gearbox) {
this.chassis = chassis;
this.engine = engine;
this.gearbox = gearbox;
}
public static Car.CarBuilder builder(){
return new Car.CarBuilder();
}
public static class CarBuilder{
/**
* 底盘
*/
private String chassis;
/**
* 发动机
*/
private String engine;
/**
* 变速箱
*/
private String gearbox;
public CarBuilder(){
}
public Car.CarBuilder chassis(String chassis) {
this.chassis = chassis;
return this;
}
public Car.CarBuilder engine(String engine) {
this.engine = engine;
return this;
}
public Car.CarBuilder gearbox(String gearbox) {
this.gearbox = gearbox;
return this;
}
/**
* 建造者通过 Car的全参构造器 来构造 Phone 实例
* @return
*/
public Car build() {
return new Car(chassis, engine, gearbox);
}
}
}
可以看到简化版 Builder Pattern 最明显的一个特点就是其不像传统 Builder Pattern 那样有多个类来扮演各个分工明显的角色。其只有一个Product类,然后通过内部类的方式实现了一个具体建造者。而Director导演这个角色也不需要了,其是通过"客户"按需的显式地指定建造流程。下面即是一个简化版的使用示例,相比较于构造器、setXxxx来进行初始化的姿势而言,链式调用的方式会显得更加简洁优雅
/**
* 简化版 Builder Patter 建造者模式 Demo
*/
public class SimpleClient {
public static void main(String[] args) {
System.out.println("-------组件比亚迪汽车-------");
Car car = Car.builder()
.chassis("比亚迪底盘!")
.engine("比亚迪发动机!")
.gearbox("比亚迪变速箱!")
.build();
System.out.println(car.toString());
}
}
最后补充一下,如果熟悉lombok插件的同学,可能知道,在Car的顶端直接加入@Builder的注解,直接就可以了。
/**
* 车
*/
@Data //lombok生成setter,getter方法
@ToString //lombok生成tostring方法
@NoArgsConstructor //lombok生成无参构造方法
@AllArgsConstructor
@Builder
public class Car {
/**
* 底盘
*/
private String chassis;
/**
* 发动机
*/
private String engine;
/**
* 变速箱
*/
private String gearbox;
用商法的测试跑出来的结果也是一样,这个插件直接帮我们实现了这个简易版的Builder Pattern模式!