建造者模式
建造者模式也称为生成器模式、构建者模式等。
简单来说,就是把对象的构造过程与对象的表示过程进行分离,使得相同的构建过程能够实现不同的对象的表示过程。
换句话来说,就是当一个对象中有很多属性参数,并且有些参数是可选参数时,可以考虑使用建造者模式。
1. 业务场景
例如,一个电脑中有 cpu、内存、usb 插孔、键盘、显示器等部件,但是只有 cpu 和内存是必须的,其它都是可选的。
java
public class Computer {
private String cpu;//必须
private String ram;//必须
private int usbCount;//可选
private String keyboard;//可选
private String display;//可选
}
2. 垃圾实现
方式一: 折叠构造函数模式(telescoping constructor pattern )
java
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 方式
java
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;
}
...
}
// 特点: 每一个属性可以单独设置。
缺点:
- 方式一由于有很多构造函数,因此在使用时很容易搞混,调试时也是跳来跳去;
- 方式二容易漏掉一些属性的设置;
3. 建造者模式实现
3.1. 略好的实现方式
这种设计方式是稍微好一些的设计方式,也是传统的 Builder 模式。这种方式是额外添加三个角色的类:
- 构建者的抽象类,这个类主要是把构建目标对象的步骤抽象出来,形成一个统一对外的方法;
- 构建者的抽象类的实现类,这个实现类根据不同的目标对象,实现具体的构建过程;
- 组装类,这个类负责实现具体的组装方法;
- Product: 最终要生成的对象,例如 Computer 实例。
- Builder: 构建者的抽象基类(有时会使用接口代替)。其定义了构建 Product 的抽象步骤,其实体类需要实现这些步骤。其会包含一个用来返回最终产品的方法 Product getProduct()。
- ConcreteBuilder: Builder 的实现类。
- Director: 决定如何构建最终产品的算法. 其会包含一个负责组装的方法 void Construct(Builder builder), 在这个方法中通过调用 builder 的方法,就可以设置 builder,等设置完成后,就可以通过 builder 的 getProduct() 方法获得最终的产品。
3.1.1. 具体实现步骤
定义目标对象类
javapublic class Computer { private String cpu;//必须 private String ram;//必须 private int usbCount;//可选 private String keyboard;//可选 private String display;//可选 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 + '\'' + '}'; } }
构建者的抽象基类
javapublic abstract class ComputerBuilder { public abstract void setUsbCount(); public abstract void setKeyboard(); public abstract void setDisplay(); public abstract Computer getComputer(); }
构建者的抽象基类的实现子类
java// 苹果电脑 public class MacComputerBuilder extends ComputerBuilder { private Computer computer; public MacComputerBuilder(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; } } // 联想电脑 public class LenovoComputerBuilder extends ComputerBuilder { private Computer computer; public LenovoComputerBuilder(String cpu, String ram) { computer=new Computer(cpu,ram); } @Override public void setUsbCount() { computer.setUsbCount(4); } @Override public void setKeyboard() { computer.setKeyboard("联想键盘"); } @Override public void setDisplay() { computer.setDisplay("联想显示器"); } @Override public Computer getComputer() { return computer; } }
组装
javapublic class ComputerDirector { public void makeComputer(ComputerBuilder builder){ builder.setUsbCount(); builder.setDisplay(); builder.setKeyboard(); } }
使用
javapublic static void main(String[] args) { ComputerDirector director=new ComputerDirector();//1 ComputerBuilder builder=new MacComputerBuilder("I5处理器","三星125");//2 director.makeComputer(builder);//3 Computer macComputer=builder.getComputer();//4 System.out.println("mac computer:"+macComputer.toString()); ComputerBuilder lenovoBuilder=new LenovoComputerBuilder("I7处理器","海力士222"); director.makeComputer(lenovoBuilder); Computer lenovoComputer=lenovoBuilder.getComputer(); System.out.println("lenovo computer:"+lenovoComputer.toString()); }
3.1.2. 扩展
如果想要新增一个华为电脑,则只需要定义一个 HuaweiComputerBuilder 类,然后实现 ComputerBuilder 类即可。
3.2. 最佳实现
上面这种方式实际上并不是最佳的实现方式,因为我们发现上面这种方式会有很多的冗余的“辅助类”,这些辅助类事实上并没有简化我们的开发。我们可以设想,如果我们把折叠构造方式和 JavaBean 方式的有点结合起来呢?于是我们的实现就变成了这样:
java
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);
}
}
//省略getter方法
}
使用时,我们只需要链式调用即可:
java
Computer computer=new Computer.Builder("因特尔","三星")
.setDisplay("三星24寸")
.setKeyboard("罗技")
.setUsbCount(2)
.build();
分析一下这种方式,我们会发现这种方式有以下特点:
- 目标类有两个特点:
- 有一个静态内部类,这个静态内部类具有和目标类同样多的参数;
- 有一个私有构造方法,参数为静态内部类;
- 静态内部类也有两个特点:
- 必选参数使用 public 标识的构造函数进行设置,可选参数则使用 setter 方式实现,并且这些 setter 的返回值均为当前对象;
- 有一个 build() 方法,这个方法中完成目标类对象的构建,并返回;
4. 实际应用
- StringBuilder
java
StringBuilder sb = new StringBuilder();
sb.append(123).append('a')
.append(1.23)
.append(true)
.append("hhhh");
- Swagger 中的 Docket(摘要) 的实现;
java
return new Docket(DocumentationType.SWAGGER_2)
.apiInfo(apiInfo())
.select()
.apis(RequestHandlerSelectors.basePackage("com.curry.springbootswagger.controller"))
.paths(PathSelectors.any())
.build();
- Mybatis 中的 SqlSessionFactoryBuilder 、XMLMapperBuilder 、XMLStatementBuilder
java
public SqlSessionFactory build(Reader reader, String environment, Properties properties) {
SqlSessionFactory var5;
try {
XMLConfigBuilder parser = new XMLConfigBuilder(reader, environment, properties);
var5 = this.build(parser.parse());
} catch (Exception var14) {
throw ExceptionFactory.wrapException("Error building SqlSession.", var14);
} finally {
ErrorContext.instance().reset();
try {
reader.close();
} catch (IOException var13) {
}
}
return var5;
}