在日常生活中,我们在烹饪一道菜、制作一款手工艺品或者生产一款手机、一台汽车、一架飞机等等,这些东西在制造过程中的工艺流程都非常复杂,购买者并不关心其是如何生成的,而生产者会根据产品的需求,虽然产品大致的生产过程及组件都是相通的,但是不同型号的产品对应的某些组件可能不一样,这个时候就需要产品生产方能够根据消费者的选择,提供一个灵活的方法来生产需求的产品,这个时候就可以使用建造者模式来进行生产。
1.建造者模式
1.1 建造者模式简介
在开发中,有时候我们需要创建出一个很复杂的对象,这个对象的创建有一个固定的步骤,并且每个步骤中会涉及到多个组件对象,这个时候就可以考虑使用建造者模式。使用建造者模式将原本复杂的对象创建过程按照规律将其分解成多个小步骤,这样在构建对象时可以灵活的选择或修改步骤。建造者模式将对象的创建和表示过程进行分离,这样我们可以使用同样的过程,只需修改这个过程中的小步骤,便能够构建出不同的对象。而对于调用方来说,我们只需要传入需要构建的类型,便能够得到需要的对象,并不需要关系创建的过程,从而实现解耦。
1.2 建造者模式结构
建造者模式的结构需要产品(Product)、抽象建造者(Builder)、具体建造者(ConcreteBuilder)、指挥者(Director)四个角色。
- 产品(Product):表明需要构建的产品对象
- 抽象建造者(Builder):抽象出来的构建者类,用于定义创建对象所需的步骤以及创建的步骤的调用过程
- 具体建造者(ConcreteBuilder):抽象建造者的具体实现,对于不同的创建过程可以用不同的类进行实现
-
指挥者(Director):使用 Builder 的类,提供给调用方使用,调用方通过使用指挥者来获取产品
// 产品类,定义产品的三个部分
public class Product {
private Object part1;
private Object part2;
private Object part3;
public void setPart1(Object part1) {
this.part1 = part1;
}
public void setPart2(Object part2) {
this.part2 = part2;
}
public void setPart3(Object part3) {
this.part3 = part3;
}
@Override
public String toString() {
return "Product{" +
"part1=" + part1 +
", part2=" + part2 +
", part3=" + part3 +
'}';
}
}
// 抽象建造者类,构建了一个产品对象,并定义了构建产品三个部分所需要的三个方法以及获取产品的方法
public abstract class Builder {
protected Product product = new Product();
public abstract void buildPart1();
public abstract void buildPart2();
public abstract void buildPart3();
public abstract Product getProduct();
}
// 具体建造者 1
public class ConcreteBuilder1 extends Builder{
@Override
public void buildPart1() {
product.setPart1("builder 1 set part 1.");
}
@Override
public void buildPart2() {
product.setPart2("builder 1 set part 2.");
}
@Override
public void buildPart3() {
product.setPart3("builder 1 set part 3.");
}
@Override
public Product getProduct() {
System.out.println("builder 1 build product.");
return product;
}
}
// 具体建造者 2
public class ConcreteBuilder2 extends Builder {
@Override
public void buildPart1() {
product.setPart1("builder 2 set part 1.");
}
@Override
public void buildPart2() {
product.setPart2("builder 2 set part 2.");
}
@Override
public void buildPart3() {
product.setPart3("builder 2 set part 3.");
}
@Override
public Product getProduct() {
System.out.println("builder 2 build product.");
return product;
}
}
// 指挥者对象
public class Director {
private Builder builder;
public Director(Builder builder) {
this.builder = builder;
}
public Product construct() {
builder.buildPart1();
builder.buildPart2();
builder.buildPart3();
Product product = builder.getProduct();
return product;
}
}
下面是调用方的代码
public static void main(String[] args) {
Director director1 = new Director(new ConcreteBuilder1());
Product product1 = director1.construct();
System.out.println(product1);
System.out.println("==================================");
Director director2 = new Director(new ConcreteBuilder2());
Product product2 = director2.construct();
System.out.println(product2);
}
控制台打印结果如下:
builder 1 build product.
Product{part1=builder 1 set part 1., part2=builder 1 set part 2., part3=builder 1 set part 3.}
==================================
builder 2 build product.
Product{part1=builder 2 set part 1., part2=builder 2 set part 2., part3=builder 2 set part 3.}
我们可以看到通过给 Director 对象传入具体的构建者便能够构建出不同的产品对象,并且构建过程对于调用方来说是不可见的。
1.3 建造者模式示例
按照惯例使用一个具体示例来加深对建造者模式的印象。我们以组装电脑主机为例,一台电脑主机通常都需要 cpu 、内存条、主板、显卡、硬盘组成,虽然组成都是一致的,但是不同价位所能达到的配置就完全不一样了,这个时候就可以使用建造者模式来模拟组装电脑。我们需要组装一台游戏电脑和一台办公电脑,那么按照上面的建造者模式结构,就需要以下几个类:
- 产品电脑类(Computer):定义了一台电脑所需要的几个组件:CPU、显卡、内存、主板和硬盘
- 抽象电脑建造者(ComputerBuilder):定义一个产品电脑及组装电脑所需配件方法
- 游戏电脑建造者(GameComputerBuilder):继承抽电脑电脑类,并实现抽象方法选择所需配件
- 办公电脑建造者(OfficeComputerBuilder):继承抽电脑电脑类,并实现抽象方法选择所需配件
- 组装电脑指挥者(ComputerDirector):在内部进行电脑组装,并给调用方返回一台组装好的电脑
具体代码如下:
// 产品电脑类
public class Computer {
private String CPU;
private String GPU;
private String memory;
private String motherboard;
private String hardDisk;
public void setCPU(String CPU) {
this.CPU = CPU;
}
public void setGPU(String GPU) {
this.GPU = GPU;
}
public void setMemory(String memory) {
this.memory = memory;
}
public void setMotherboard(String motherboard) {
this.motherboard = motherboard;
}
public void setHardDisk(String hardDisk) {
this.hardDisk = hardDisk;
}
@Override
public String toString() {
return "you have a computer:\n" +
"\t CPU: " + CPU + "\n" +
"\t GPU: " + GPU + "\n" +
"\t memory: " + memory + "\n" +
"\t motherboard: " + motherboard + "\n" +
"\t hardDisk: " + hardDisk + "\n";
}
}
// 抽象电脑建造者
public abstract class ComputerBuilder {
protected Computer computer = new Computer();
public abstract void CPU();
public abstract void GPU();
public abstract void memory();
public abstract void motherboard();
public abstract void hardDisk();
public abstract Computer getComputer();
}
// 游戏电脑建造者
public class GameComputerBuilder extends ComputerBuilder {
@Override
public void CPU() {
computer.setCPU("i9-12900K");
}
@Override
public void GPU() {
computer.setGPU("RTX 3090 Ti");
}
@Override
public void memory() {
computer.setMemory("64GB");
}
@Override
public void motherboard() {
computer.setMotherboard("Z590 AORUS MASTER");
}
@Override
public void hardDisk() {
computer.setHardDisk("2TB SSD");
}
@Override
public Computer getComputer() {
return computer;
}
}
// 办公电脑建造者
public class OfficeComputerBuilder extends ComputerBuilder {
@Override
public void CPU() {
computer.setCPU("i7-7700k");
}
@Override
public void GPU() {
computer.setGPU("GTX 1050 Ti");
}
@Override
public void memory() {
computer.setMemory("32GB");
}
@Override
public void motherboard() {
computer.setMotherboard("ASUS B560M-PLUS");
}
@Override
public void hardDisk() {
computer.setHardDisk("1TB SSD");
}
@Override
public Computer getComputer() {
return computer;
}
}
// 组装电脑指挥者
public class Director {
private Builder builder;
public Director(Builder builder) {
this.builder = builder;
}
public Product construct() {
builder.buildPart1();
builder.buildPart2();
builder.buildPart3();
Product product = builder.getProduct();
return product;
}
}
我们需要组装一台游戏电脑和办公电脑,通常情况下虽然电脑的组装所需部件都是一样的,但是游戏电脑的配置性能都会比办公电脑高,因此需要两个不同的具体构建者来给电脑的配置进行选择,调用方的代码如下:
public static void main(String[] args) {
ComputerDirector computerDirector = new ComputerDirector(new GameComputerBuilder());
Computer gameComputer = computerDirector.construct();
System.out.println(gameComputer);
System.out.println("==================================");
ComputerDirector computerDirector1 = new ComputerDirector(new OfficeComputerBuilder());
Computer officeComputer = computerDirector1.construct();
System.out.println(officeComputer);
}
执行上面代码,控制台打印如下:
you have a computer:
CPU: i9-12900K
GPU: RTX 3090 Ti
memory: 64GB
motherboard: Z590 AORUS MASTER
hardDisk: 2TB SSD
==================================
you have a computer:
CPU: i7-7700k
GPU: GTX 1050 Ti
memory: 32GB
motherboard: ASUS B560M-PLUS
hardDisk: 1TB SSD
从上面的代码可以看到,在调用方通过传入不同的电脑建造者,而获取到了两台不同配置的电脑。
2. 建造者模式在框架源码中的应用
2.1 建造者模式在 JDK 中的应用
JDK 中的建造者模式使用最多的就是 StringBuilder 类,StringBuilder 继承自 AbstractStringBuilder,而我们每次在调用 append 方法的时候就是在往 AbstractStringBuilder 类中变量 value 中追加字符,所以此时 AbstractStringBuilder 就对应抽象建造者,StringBuilder 就是具体的建造者,String 对象就是我们所需要的产品。但是此时我们并没有发现 Director,其实此时的 StringBuilder 类同时也充当着 Director 的角色,其 toString() 方法就是返回最终 String 对象。
abstract class AbstractStringBuilder implements Appendable, CharSequence {
/**
* The value is used for character storage.
*/
char[] value;
/**
* The count is the number of characters used.
*/
int count;
……
@Override
public AbstractStringBuilder append(char c) {
ensureCapacityInternal(count + 1);
value[count++] = c;
return this;
}
……
}
public final class StringBuilder
extends AbstractStringBuilder
implements java.io.Serializable, CharSequence
{
……
@Override
public StringBuilder append(char c) {
super.append(c);
return this;
}
}
……
@Override
public String toString() {
// Create a copy, don't share the array
return new String(value, 0, count);
}
}
2.2 Lombok 中的 @Builder
我们在使用 Lombok 时有个基础注解叫 @Builder,使用该注解的类就不用再 new 对象,直接赋值然后调用 build() 方法便能构建对象。其原理就是在使用该注解的类中生成一个静态的内部 Builder类,然后通过调用该内部类的方法给生成的类对象赋值。我们以 Computer 类为例,代码如下:
@Builder
public class Computer {
private String CPU;
private String GPU;
private String memory;
private String motherboard;
private String hardDisk;
}
对代码进行编译后,再看最后生成的代码如下
public class Computer {
private String CPU;
private String GPU;
private String memory;
private String motherboard;
private String hardDisk;
Computer(String CPU, String GPU, String memory, String motherboard, String hardDisk) {
this.CPU = CPU;
this.GPU = GPU;
this.memory = memory;
this.motherboard = motherboard;
this.hardDisk = hardDisk;
}
public static Computer.ComputerBuilder builder() {
return new Computer.ComputerBuilder();
}
public static class ComputerBuilder {
private String CPU;
private String GPU;
private String memory;
private String motherboard;
private String hardDisk;
ComputerBuilder() {
}
public Computer.ComputerBuilder CPU(String CPU) {
this.CPU = CPU;
return this;
}
public Computer.ComputerBuilder GPU(String GPU) {
this.GPU = GPU;
return this;
}
public Computer.ComputerBuilder memory(String memory) {
this.memory = memory;
return this;
}
public Computer.ComputerBuilder motherboard(String motherboard) {
this.motherboard = motherboard;
return this;
}
public Computer.ComputerBuilder hardDisk(String hardDisk) {
this.hardDisk = hardDisk;
return this;
}
public Computer build() {
return new Computer(this.CPU, this.GPU, this.memory, this.motherboard, this.hardDisk);
}
public String toString() {
return "Computer.ComputerBuilder(CPU=" + this.CPU + ", GPU=" + this.GPU + ", memory=" + this.memory + ", motherboard=" + this.motherboard + ", hardDisk=" + this.hardDisk + ")";
}
}
}
可以看到反编译后内部生成了 ComputerBuilder 类,该类就是用于构建 Computer 对象,因此这也是一个构建者模式。
3. 总结
建造者模式将对象的创建过程和表现分离,并且调用方通过指挥者调用方法对对象进行构建,使得调用方不再关心对象构建过程,构建对象的具体过程可以根据传入类型的不同而改变。通常在实际开发应用中通常会对建造者模式的角色进行阉割,往往只保留真正构建对象的过程。那么有的人就可能会有疑问了,构建者模式最终是获取一个对象,工厂模式也是获取一个对象,这两种模式有什么区别呢?这两种模式都是属于创建型模式,而这两种模式的侧重点不太一样:
- 建造者模式用于复杂对象的构建,并且对象中的复杂组件的调用和赋值过程能够自定义;工厂模式所创建出来的对象都是以严格的
- 建造者模式在构建的过程需要知道对象需要哪些组件,并对组件进行赋值;工厂模式并不关心组件,直接调用方法即可
- 从建造者模式的概念上讲,其实建造者模式是在一个固定的流程下构建的对象,因此强调一定的执行顺序,而工厂模式并不关心。但是在开发中往往会忽略掉这部分的执行顺序
两种模式在实际开发中的使用需要根据所需场景进行抉择。