建造者模式的目的就是构造一个复杂对象。将一个复杂对象的构建与它的表示(属性赋值)分离,使得同样的构建过程可以创建不同的对象。
那么什么是复杂对象?简单来说,复杂对象是指那些包含多个成员属性的对象,这些成员属性也称为部件或零件,如汽车包括方向盘、发动机、轮胎等部件,电子邮件包括发件人、收件人、主题、内容、附件等部件,一个典型的复杂对象类代码示例如下:
class Product {private String partA; //定义部件,部件可以是任意类型,包括值类型和引用类型private String partB;private String partC;//partA的Getter方法和Setter方法省略//partB的Getter方法和Setter方法省略//partC的Getter方法和Setter方法省略}
简单来说就是对象的创建和属性赋值分离开来,由具体的ConcreteBuilder去赋值,用户只关心选择不同的ConcreteBuilder就可以创建出不同的对象。
传统建造者模式
在抽象建造者类中定义了产品的创建方法和返回方法,其典型代码如下:
abstract class Builder {//创建产品对象protected Product product=new Product();public abstract void buildPartA();public abstract void buildPartB();public abstract void buildPartC();//返回产品对象public Product getResult() {return product;}}
在抽象类Builder中声明了一系列抽象的buildPartX()方法用于创建复杂产品的各个部件,具体建造过程在ConcreteBuilder中实现,此外还提供了工厂方法getResult(),用于返回一个建造好的完整产品。
在ConcreteBuilder中实现了buildPartX()方法,通过调用Product的setPartX()方法可以给产品对象的成员属性设值。不同的具体建造者在实现buildPartX()方法时将有所区别,如setPartX()方法的参数可能不一样,在有些具体建造者类中某些setPartX()方法无须实现(提供一个空实现)。而这些对于客户端来说都无须关心,客户端只需知道具体建造者类型即可。
在建造者模式的结构中还引入了一个指挥者类Director,该类主要有两个作用:一方面它隔离了客户与创建过程;另一方面它控制产品的创建过程,包括某个buildPartX()方法是否被调用以及多个buildPartX()方法调用的先后次序等。指挥者针对抽象建造者编程,客户端只需要知道具体建造者的类型,即可通过指挥者类调用建造者的相关方法,返回一个完整的产品对象。在实际生活中也存在类似指挥者一样的角色,如一个客户去购买电脑,电脑销售人员相当于指挥者,只要客户确定电脑的类型,电脑销售人员可以通知电脑组装人员给客户组装一台电脑。指挥者类的代码示例如下:
class Director {private Builder builder;public Director(Builder builder) {this.builder=builder;}public void setBuilder(Builder builder) {this.builder=builer;}//产品构建与组装方法public Product construct() {builder.buildPartA();builder.buildPartB();builder.buildPartC();return builder.getResult();}}
在指挥者类中可以注入一个抽象建造者类型的对象,其核心在于提供了一个建造方法construct(),在该方法中调用了builder对象的构造部件的方法,最后返回一个产品对象。
对于客户端而言,只需关心具体的建造者即可,一般情况下,客户端类代码片段如下所示:
……Builder builder = new ConcreteBuilder(); //可通过配置文件实现Director director = new Director(builder);Product product = director.construct();……
链式构建对象
当一个类的构造函数参数超过4个,而且这些参数有些是可选的时,我们通常有两种办法来构建它的对象。 例如我们现在有如下一个类计算机类Computer,其中cpu与ram是必填参数,而其他3个是可选参数,那么我们如何构造这个类的实例呢,通常有两种常用的方式:
public class Computer {private String cpu;//必须private String ram;//必须private int usbCount;//可选private String keyboard;//可选private String display;//可选}
第一:折叠构造函数模式(telescoping constructor pattern ),这个我们经常用,如下代码所示
public class Computer {...public Computer(String cpu, String ram) {this(cpu, ram, 0);}public Computer(String cpu, String ram, int usbCount) {this(cpu, ram, usbCount, "罗技键盘");}public Computer(String cpu, String ram, int usbCount, String keyboard) {this(cpu, ram, usbCount, keyboard, "三星显示器");}public Computer(String cpu, String ram, int usbCount, String keyboard, String display) {this.cpu = cpu;this.ram = ram;this.usbCount = usbCount;this.keyboard = keyboard;this.display = display;}}
第二种:Javabean 模式,如下所示
public class Computer {...public String getCpu() {return cpu;}public void setCpu(String cpu) {this.cpu = cpu;}public String getRam() {return ram;}public void setRam(String ram) {this.ram = ram;}public int getUsbCount() {return usbCount;}...}
那么这两种方式有什么弊端呢? 第一种主要是使用及阅读不方便。你可以想象一下,当你要调用一个类的构造函数时,你首先要决定使用哪一个,然后里面又是一堆参数,如果这些参数的类型很多又都一样,你还要搞清楚这些参数的含义,很容易就传混了。。。那酸爽谁用谁知道。 第二种方式在构建过程中对象的状态容易发生变化,造成错误。因为那个类中的属性是分步设置的,所以就容易出错。
为了解决这两个痛点,builder模式就横空出世了。
如何实现
- 在Computer 中创建一个静态内部类 Builder,然后将Computer 中的参数都复制到Builder类中。
- 在Computer中创建一个private的构造函数,参数为Builder类型
- 在Builder中创建一个
public的构造函数,参数为Computer中必填的那些参数,cpu 和ram。 - 在Builder中创建设置函数,对Computer中那些可选参数进行赋值,返回值为Builder类型的实例
- 在Builder中创建一个
build()方法,在其中构建Computer的实例并返回
下面代码就是最终的样子
public class Computer {private final String cpu;//必须private final String ram;//必须private final int usbCount;//可选private final String keyboard;//可选private final String display;//可选private Computer(Builder builder){this.cpu=builder.cpu;this.ram=builder.ram;this.usbCount=builder.usbCount;this.keyboard=builder.keyboard;this.display=builder.display;}public static class Builder{private String cpu;//必须private String ram;//必须private int usbCount;//可选private String keyboard;//可选private String display;//可选public Builder(String cup,String ram){this.cpu=cup;this.ram=ram;}public Builder setUsbCount(int usbCount) {this.usbCount = usbCount;return this;}public Builder setKeyboard(String keyboard) {this.keyboard = keyboard;return this;}public Builder setDisplay(String display) {this.display = display;return this;}public Computer build(){return new Computer(this);}}}
如何使用
在客户端使用链式调用,一步一步的把对象构建出来。
Computer computer=new Computer.Builder("因特尔","三星").setDisplay("三星24寸").setKeyboard("罗技").setUsbCount(2).build();
这种建造者模式首先省略了director 这个角色,将构建算法交给了client端,其次将builder 写到了要构建的产品类里面,最后采用了链式调用。
@Builder
lombok下的Builder注解提供了构建对象的简单方式
package com.zstu.builder;import lombok.Builder;import lombok.Data;@Builder@Datapublic class Product {private String part1;private String part2;}public class Test {public static void main(String[] args) {Product product = Product.builder().part1("零件1").part2("零件2").build();System.out.println(product); // Product(part1=零件1, part2=零件2)}}
