建造者模式的目的就是构造一个复杂对象。将一个复杂对象的构建与它的表示(属性赋值)分离,使得同样的构建过程可以创建不同的对象。

那么什么是复杂对象?简单来说,复杂对象是指那些包含多个成员属性的对象,这些成员属性也称为部件或零件,如汽车包括方向盘、发动机、轮胎等部件,电子邮件包括发件人、收件人、主题、内容、附件等部件,一个典型的复杂对象类代码示例如下:

  1. class Product {
  2. private String partA; //定义部件,部件可以是任意类型,包括值类型和引用类型
  3. private String partB;
  4. private String partC;
  5. //partA的Getter方法和Setter方法省略
  6. //partB的Getter方法和Setter方法省略
  7. //partC的Getter方法和Setter方法省略
  8. }

简单来说就是对象的创建和属性赋值分离开来,由具体的ConcreteBuilder去赋值,用户只关心选择不同的ConcreteBuilder就可以创建出不同的对象。

传统建造者模式

在抽象建造者类中定义了产品的创建方法和返回方法,其典型代码如下:

  1. abstract class Builder {
  2. //创建产品对象
  3. protected Product product=new Product();
  4. public abstract void buildPartA();
  5. public abstract void buildPartB();
  6. public abstract void buildPartC();
  7. //返回产品对象
  8. public Product getResult() {
  9. return product;
  10. }
  11. }

在抽象类Builder中声明了一系列抽象的buildPartX()方法用于创建复杂产品的各个部件,具体建造过程在ConcreteBuilder中实现,此外还提供了工厂方法getResult(),用于返回一个建造好的完整产品。

在ConcreteBuilder中实现了buildPartX()方法,通过调用Product的setPartX()方法可以给产品对象的成员属性设值。不同的具体建造者在实现buildPartX()方法时将有所区别,如setPartX()方法的参数可能不一样,在有些具体建造者类中某些setPartX()方法无须实现(提供一个空实现)。而这些对于客户端来说都无须关心,客户端只需知道具体建造者类型即可。

在建造者模式的结构中还引入了一个指挥者类Director,该类主要有两个作用:一方面它隔离了客户与创建过程;另一方面它控制产品的创建过程,包括某个buildPartX()方法是否被调用以及多个buildPartX()方法调用的先后次序等。指挥者针对抽象建造者编程,客户端只需要知道具体建造者的类型,即可通过指挥者类调用建造者的相关方法,返回一个完整的产品对象。在实际生活中也存在类似指挥者一样的角色,如一个客户去购买电脑,电脑销售人员相当于指挥者,只要客户确定电脑的类型,电脑销售人员可以通知电脑组装人员给客户组装一台电脑。指挥者类的代码示例如下:

  1. class Director {
  2. private Builder builder;
  3. public Director(Builder builder) {
  4. this.builder=builder;
  5. }
  6. public void setBuilder(Builder builder) {
  7. this.builder=builer;
  8. }
  9. //产品构建与组装方法
  10. public Product construct() {
  11. builder.buildPartA();
  12. builder.buildPartB();
  13. builder.buildPartC();
  14. return builder.getResult();
  15. }
  16. }

在指挥者类中可以注入一个抽象建造者类型的对象,其核心在于提供了一个建造方法construct(),在该方法中调用了builder对象的构造部件的方法,最后返回一个产品对象。

对于客户端而言,只需关心具体的建造者即可,一般情况下,客户端类代码片段如下所示:

  1. ……
  2. Builder builder = new ConcreteBuilder(); //可通过配置文件实现
  3. Director director = new Director(builder);
  4. Product product = director.construct();
  5. ……

链式构建对象

当一个类的构造函数参数超过4个,而且这些参数有些是可选的时,我们通常有两种办法来构建它的对象。 例如我们现在有如下一个类计算机类Computer,其中cpu与ram是必填参数,而其他3个是可选参数,那么我们如何构造这个类的实例呢,通常有两种常用的方式:

  1. public class Computer {
  2. private String cpu;//必须
  3. private String ram;//必须
  4. private int usbCount;//可选
  5. private String keyboard;//可选
  6. private String display;//可选
  7. }

第一:折叠构造函数模式(telescoping constructor pattern ),这个我们经常用,如下代码所示

  1. public class Computer {
  2. ...
  3. public Computer(String cpu, String ram) {
  4. this(cpu, ram, 0);
  5. }
  6. public Computer(String cpu, String ram, int usbCount) {
  7. this(cpu, ram, usbCount, "罗技键盘");
  8. }
  9. public Computer(String cpu, String ram, int usbCount, String keyboard) {
  10. this(cpu, ram, usbCount, keyboard, "三星显示器");
  11. }
  12. public Computer(String cpu, String ram, int usbCount, String keyboard, String display) {
  13. this.cpu = cpu;
  14. this.ram = ram;
  15. this.usbCount = usbCount;
  16. this.keyboard = keyboard;
  17. this.display = display;
  18. }
  19. }

第二种:Javabean 模式,如下所示

  1. public class Computer {
  2. ...
  3. public String getCpu() {
  4. return cpu;
  5. }
  6. public void setCpu(String cpu) {
  7. this.cpu = cpu;
  8. }
  9. public String getRam() {
  10. return ram;
  11. }
  12. public void setRam(String ram) {
  13. this.ram = ram;
  14. }
  15. public int getUsbCount() {
  16. return usbCount;
  17. }
  18. ...
  19. }

那么这两种方式有什么弊端呢? 第一种主要是使用及阅读不方便。你可以想象一下,当你要调用一个类的构造函数时,你首先要决定使用哪一个,然后里面又是一堆参数,如果这些参数的类型很多又都一样,你还要搞清楚这些参数的含义,很容易就传混了。。。那酸爽谁用谁知道。 第二种方式在构建过程中对象的状态容易发生变化,造成错误。因为那个类中的属性是分步设置的,所以就容易出错。

为了解决这两个痛点,builder模式就横空出世了。

如何实现

  1. 在Computer 中创建一个静态内部类 Builder,然后将Computer 中的参数都复制到Builder类中。
  2. 在Computer中创建一个private的构造函数,参数为Builder类型
  3. 在Builder中创建一个public的构造函数,参数为Computer中必填的那些参数,cpu 和ram。
  4. 在Builder中创建设置函数,对Computer中那些可选参数进行赋值,返回值为Builder类型的实例
  5. 在Builder中创建一个build()方法,在其中构建Computer的实例并返回

下面代码就是最终的样子

  1. public class Computer {
  2. private final String cpu;//必须
  3. private final String ram;//必须
  4. private final int usbCount;//可选
  5. private final String keyboard;//可选
  6. private final String display;//可选
  7. private Computer(Builder builder){
  8. this.cpu=builder.cpu;
  9. this.ram=builder.ram;
  10. this.usbCount=builder.usbCount;
  11. this.keyboard=builder.keyboard;
  12. this.display=builder.display;
  13. }
  14. public static class Builder{
  15. private String cpu;//必须
  16. private String ram;//必须
  17. private int usbCount;//可选
  18. private String keyboard;//可选
  19. private String display;//可选
  20. public Builder(String cup,String ram){
  21. this.cpu=cup;
  22. this.ram=ram;
  23. }
  24. public Builder setUsbCount(int usbCount) {
  25. this.usbCount = usbCount;
  26. return this;
  27. }
  28. public Builder setKeyboard(String keyboard) {
  29. this.keyboard = keyboard;
  30. return this;
  31. }
  32. public Builder setDisplay(String display) {
  33. this.display = display;
  34. return this;
  35. }
  36. public Computer build(){
  37. return new Computer(this);
  38. }
  39. }
  40. }

如何使用

在客户端使用链式调用,一步一步的把对象构建出来。

  1. Computer computer=new Computer.Builder("因特尔","三星")
  2. .setDisplay("三星24寸")
  3. .setKeyboard("罗技")
  4. .setUsbCount(2)
  5. .build();

这种建造者模式首先省略了director 这个角色,将构建算法交给了client端,其次将builder 写到了要构建的产品类里面,最后采用了链式调用。

@Builder

lombok下的Builder注解提供了构建对象的简单方式

  1. package com.zstu.builder;
  2. import lombok.Builder;
  3. import lombok.Data;
  4. @Builder
  5. @Data
  6. public class Product {
  7. private String part1;
  8. private String part2;
  9. }
  10. public class Test {
  11. public static void main(String[] args) {
  12. Product product = Product.builder()
  13. .part1("零件1")
  14. .part2("零件2")
  15. .build();
  16. System.out.println(product); // Product(part1=零件1, part2=零件2)
  17. }
  18. }

参考