一、概述
在软件开发过程中有时需要创建一个复杂的对象,这个复杂对象通常由多个子部件按一定的步骤组合而成。
例如,计算机是由 CPU、主板、内存、硬盘、显卡、机箱、显示器、键盘、鼠标等部件组装而成的,采购员不可能自己去组装计算机,而是将计算机的配置要求告诉计算机销售公司,计算机销售公司安排技术人员去组装计算机,然后再交给要买计算机的采购员。
一个产品都是由多个部件构成的,各个部件可以灵活选择,但其创建步骤都大同小异。这类产品的创建无法用前面介绍的工厂模式描述,只有建造者模式可以很好地描述该类产品的创建。
二、定义
指将一个复杂对象的构造与它的表示分离,使同样的构建过程可以创建不同的表示,这样的设计模式被称为建造者模式。它是将一个复杂的对象分解为多个简单的对象,然后一步一步构建而成。它将变与不变相分离,即产品的组成部分是不变的,但每一部分是可以灵活选择的。
正如概述中说的,技术人员也是一步一步的去组装电脑,可以组装高配版的电脑,也可组装低配版的电脑,但是电脑内部的产品是固定的
三、特点
1、优点
- 封装性好,构建和表示分离。
- 扩展性好,各个具体的建造者相互独立,有利于系统的解耦。
- 客户端不必知道产品内部组成的细节,建造者可以对创建过程逐步细化,而不对其它模块产生任何影响,便于控制细节风险。
2、缺点
- 产品的组成部分必须相同,这限制了其使用范围。
- 如果产品的内部变化复杂,如果产品内部发生变化,则建造者也要同步修改,后期维护成本较大。
建造者(Builder)模式和工厂模式的关注点不同:建造者模式注重零部件的组装过程,而工厂方法模式更注重零部件的创建过程,但两者可以结合使用。
四、模式的结构
建造者(Builder)模式由产品、抽象建造者、具体建造者、指挥者等 4 个要素构成
- 产品角色(Product):它是包含多个组成部件的复杂对象,由具体建造者来创建其各个零部件。
- 抽象建造者(Builder):它是一个包含创建产品各个子部件的抽象方法的接口,通常还包含一个返回复杂产品的方法 getResult()。
- 具体建造者(Concrete Builder):实现 Builder 接口,完成复杂产品的各个部件的具体创建方法。
- 指挥者(Director):它调用建造者对象中的部件构造与装配方法完成复杂对象的创建,在指挥者中不涉及具体产品的信息。
五、解决的问题
当一个类的构造函数参数超过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模式就横空出世了。
六、代码实现demo
package build;
/**
* @Package: build
* @ClassName: Computer
* @Author: tanp
* @Description: 产品角色
* @Date: 2020/11/2 10:50
*/
public class Computer {
private String cpu;
private String ram;
/**以下三个为可选参数*/
private int usbCount;
private String keyboard;
private String display;
/**
* @Description 必选参数构造函数
* @Date 2020/11/2 10:54
* @Author tanp
*/
public Computer(String cpu,String ram){
this.cpu = cpu;
this.ram = ram;
}
public void setUsbCount(int usbCount) {
this.usbCount = usbCount;
}
public void setKeyboard(String keyboard) {
this.keyboard = keyboard;
}
public void setDisplay(String display) {
this.display = display;
}
@Override
public String toString() {
return "Computer{" +
"cpu='" + cpu + '\'' +
", ram='" + ram + '\'' +
", usbCount=" + usbCount +
", keyboard='" + keyboard + '\'' +
", display='" + display + '\'' +
'}';
}
}
package build;
/**
* @Package: build
* @ClassName: ComputerBuilder
* @Author: tanp
* @Description: 抽象建造者
* @Date: 2020/11/2 11:02
*/
public abstract class ComputerBuilder {
public abstract void setUsbCount();
public abstract void setKeyboard();
public abstract void setDisplay();
public abstract Computer getComputer();
}
package build;
/**
* @Package: build
* @ClassName: DaierComputerBuilder
* @Author: tanp
* @Description: 具体建造者
* @Date: 2020/11/2 11:04
*/
public class DaierComputerBuilder extends ComputerBuilder{
private Computer computer;
public DaierComputerBuilder(String cpu,String ram){
computer = new Computer(cpu,ram);
}
@Override
public void setUsbCount() {
computer.setUsbCount(3);
}
@Override
public void setKeyboard() {
computer.setKeyboard("戴尔键盘");
}
@Override
public void setDisplay() {
computer.setDisplay("戴尔显示器");
}
@Override
public Computer getComputer() {
return computer;
}
}
package build;
/**
* @Package: build
* @ClassName: XiaomiComputerBuilder
* @Author: tanp
* @Description: 具体建造者
* @Date: 2020/11/2 11:05
*/
public class XiaomiComputerBuilder extends ComputerBuilder{
private Computer computer;
public XiaomiComputerBuilder(String cpu,String ram){
computer = new Computer(cpu,ram);
}
@Override
public void setUsbCount() {
computer.setUsbCount(2);
}
@Override
public void setKeyboard() {
computer.setKeyboard("小米键盘");
}
@Override
public void setDisplay() {
computer.setDisplay("小米显示器");
}
@Override
public Computer getComputer() {
return computer;
}
}
package build;
/**
* @Package: build
* @ClassName: ComputerDirector
* @Author: tanp
* @Description: 指挥者
* @Date: 2020/11/2 11:11
*/
public class ComputerDirector {
public void makeComputer(ComputerBuilder computerBuilder){
computerBuilder.setDisplay();
computerBuilder.setKeyboard();
computerBuilder.setUsbCount();
}
}
package build;
/**
* @Package: build
* @ClassName: BuildMain
* @Author: tanp
* @Description: 使用者
* @Date: 2020/11/2 11:12
*/
public class BuildMain {
public static void main(String[] args) {
//获取指挥者
ComputerDirector computerDirector = new ComputerDirector();
//获取具体建造者1
ComputerBuilder xiaomiComputerBuilder = new XiaomiComputerBuilder("小米cpu","小米ram");
computerDirector.makeComputer(xiaomiComputerBuilder);
//通过具体构建者获取商品
Computer xiaomi = xiaomiComputerBuilder.getComputer();
System.out.println("小米:"+xiaomi.toString());
//获取具体构建者2
ComputerBuilder daierComputerBuilder = new DaierComputerBuilder("戴尔cpu","戴尔ram");
computerDirector.makeComputer(daierComputerBuilder);
//通过具体构建者获取商品
Computer daier = daierComputerBuilder.getComputer();
System.out.println("戴尔:"+daier.toString());
}
}
运行结果:
七、简单使用demo
- 在产品类(Computer) 中创建一个静态内部类 Builder,然后将产品(Computer) 中的参数都复制到Builder类中。
- 在产品类(Computer) 中创建一个private的构造函数,参数为Builder类型
- 在Builder中创建一个
public
的构造函数,参数为产品类(Computer) 中必填的那些参数 - 在Builder中创建设置函数,对产品类(Computer) 中那些可选参数进行赋值,返回值为Builder类型的实例
- 在Builder中创建一个
build()
方法,在其中构建产品类(Computer) 的实例并返回
computer
package build.V1;
/**
* @Package: build.V1
* @ClassName: Computer
* @Author: tanp
* @Description: ${description}
* @Date: 2020/11/2 11:35
*/
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 cpu, String ram) {
this.cpu = cpu;
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);
}
}
@Override
public String toString() {
return "Computer{" +
"cpu='" + cpu + '\'' +
", ram='" + ram + '\'' +
", usbCount=" + usbCount +
", keyboard='" + keyboard + '\'' +
", display='" + display + '\'' +
'}';
}
}
package build.V1;
/**
* @Package: build.V1
* @ClassName: ComputerMain
* @Author: tanp
* @Description: ${description}
* @Date: 2020/11/2 11:42
*/
public class ComputerMain {
public static void main(String[] args) {
Computer computer = new Computer.Builder("联想cpu","联想ram")
.setDisplay("联想显示屏")
.setKeyboard("联想键盘")
.setUsbCount(3)
.build();
System.out.println("联想:"+computer.toString());
}
}
相比于前面的四个角色,简化模式首先其省略了director 这个角色,将构建算法交给了client端,其次将builder 写到了要构建的产品类里面,最后采用了链式调用