# 建造者模式
1.简介
建造者模式是一种创建型设计模式,使你能够分步骤创建复杂对象。该模式允许你使用相关的创建代码生成不同类型和形式的对象。
2.uml图
3.示例
首先我们来看一看传统的建造者模式:
Product: 最终要生成的对象,例如 Computer实例。
Builder: 构建者的抽象基类(有时会使用接口代替)。其定义了构建Product的抽象步骤,其实体类需要实现这些步骤。其会包含一个用来返回最终产品的方法Product getProduct()。
ConcreteBuilder: Builder的实现类。
Director: 决定如何构建最终产品的算法. 其会包含一个负责组装的方法void Construct(Builder builder), 在这个方法中通过调用builder的方法,就可以设置builder,等设置完成后,就可以通过builder的 getProduct() 方法获得最终的产品。
主要核心就是将一系列的生成步骤(Builder)和创建产品的一系列生成器步骤调用(Director)抽象出来,使其解耦开来。严格来说程序中并非一定需要Director对象,但是Director中非常适合放入各种例行构造流程, 以便在程序中反复使用。
Computer
类:
package com.gs.designmodel.builder.tradition;
/**
* @author: Gaos
* @Date: 2023-07-19 16:37
*
* 目标电脑类
**/
public 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 + ''' +
'}';
}
}
抽象构建者类:
package com.gs.designmodel.builder.tradition;
/**
* @author: Gaos
* @Date: 2023-07-19 16:46
*
* 抽象构建者类
**/
public abstract class ComputerBuilder {
public abstract void setUsbCount();
public abstract void setKeyBoard();
public abstract void setDisplay();
public abstract Computer getComputer();
}
实体构建者类--->苹果电脑构建者类
package com.gs.designmodel.builder.tradition;
/**
* @author: Gaos
* @Date: 2023-07-19 16:47
*
* 苹果电脑构建者类
**/
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("Mac键盘");
}
@Override
public void setDisplay() {
computer.setDisplay("Mac显示器");
}
@Override
public Computer getComputer() {
return computer;
}
}
实体构建者类-->联想电脑构建者类
package com.gs.designmodel.builder.tradition;
/**
* @author: Gaos
* @Date: 2023-07-19 16:50
**/
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("Lenovo键盘");
}
@Override
public void setDisplay() {
computer.setDisplay("Lenovo显示器");
}
@Override
public Computer getComputer() {
return computer;
}
}
Director
指导者类
package com.gs.designmodel.builder.tradition;
/**
* @author: Gaos
* @Date: 2023-07-19 16:56
*
* 指导者类(Director)
**/
public class ComputerDirector {
public void makeComputer(ComputerBuilder builder) {
builder.setUsbCount();
builder.setDisplay();
builder.setKeyBoard();
}
}
测试:
package com.gs.designmodel.builder.tradition;
/**
* @author: Gaos
* @Date: 2023-07-19 16:57
*
* 首先生成一个director->生成一个目标builder->使用director组装builder->组装完毕后创建产品实例
**/
public class Test {
public static void main(String[] args) {
ComputerDirector director = new ComputerDirector();
MacComputerBuilder builder = new MacComputerBuilder("I5处理器", "三星122");
director.makeComputer(builder);
Computer macComputer = builder.getComputer();
System.out.println("mac computer :" + macComputer.toString());
LenovoComputerBuilder lenovoBuilder = new LenovoComputerBuilder("I7处理器", "海尔155");
director.makeComputer(lenovoBuilder);
Computer lenovoComputer = lenovoBuilder.getComputer();
System.out.println("lenovo computer: " + lenovoComputer.toString());
}
}
结果:
mac computer :Computer{cpu='I5处理器', ram='三星122', usbCount=2, keyBoard='Mac键盘', display='Mac显示器'}
lenovo computer: Computer{cpu='I7处理器', ram='海尔155', usbCount=4, keyBoard='Lenovo键盘', display='Lenovo显示器'}
再下来我们再来看一下目前比较流行的建造者模式:
首先我们上述电脑类中有两个必选参数,cpu
ram
。其他三个为非必选参数,那么我们通常需要如何去构造电脑类的实例呢。
- 首先第一种方式就是构造方法,通过构造函数去实现对象的创建。但是当参数类型数量过多的时候,不同的实例所需要的构造函数也不同,就会导致构造函数特别多。同时如果需要构造函数的参数类型都是一致的,比如说都为
int
或String
就会导致参数顺序十分容易写错,接手的人维护也不易。 - 第二种方式就是 使用
set
方法构造实例,其实也就是使用构造函数(空的或者带必选参数的)方法去生成实例并含有必要的属性,再使用set
方法去对其他属性赋值。这种对象的创建方式可以有效的防止赋值属性错乱的问题,因为看上去一目了然,基本上不会出错,代码可读性也很强。但是一方面如果字段多了,对应的代码行数也会比较多。另一种是如果我希望这个对象构建完后,就不可以再被修改了,这一点也是set
方法做不到的,因为他就是提供给调用者使用的,在这种情况下可能会存在一些安全隐患。
在这中情况下一种新型的建造者模式就出现了,类似像lombok中给对象加了@Builder注解之后的创建方式。因为lombok中的@Builder就是建造者模式。我们使用这种方式来对上面的代码进行实现。
大概的思路:
1、在Computer 中创建一个静态内部类 Builder,然后将Computer 中的参数都复制到Builder类中。
2、在Computer中创建一个private的构造函数,参数为Builder类型
3、在Builder中创建一个public的构造函数,参数为Computer中必填的那些参数,cpu 和ram。
4、在Builder中创建设置函数,对Computer中那些可选参数进行赋值,返回值为Builder类型的实例
5、在Builder中创建一个build()方法,在其中构建Computer的实例并返回
按照上述的思路改造原本的 Computer
类:
package com.gs.designmodel.builder.popularity;
/**
* @author: Gaos
* @Date: 2023-07-20 10:49
*
* 链式调用的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 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);
}
}
}
同时返回自身来实现链式调用,下面来进行一下测试类:
package com.gs.designmodel.builder.popularity;
/**
* @author: Gaos
* @Date: 2023-07-20 11:35
**/
public class Test {
public static void main(String[] args) {
Computer computer=new Computer.Builder("因特尔","三星")
.setDisplay("三星24寸")
.setKeyboard("罗技")
.setUsbCount(2)
.build();
}
}
结果:
Computer{cpu='因特尔', ram='三星', usbCount=2, keyBoard='罗技', display='三星24寸'}
可能有人会有疑惑,为什么不能写成这种模式:
Computer = new Computer("因特尔","三星")
.setDisplay("三星24寸")
.setKeyboard("罗技")
.setUsbCount(2);
这里我个人的理解是建造者模式是不能依赖于set
方法的,它需要对构建过程闭合不能向外暴露方法。假如lombok中的@Builder中使用的是这种,那不就以为着使用这个注解的对象默认对外暴露所有的修改方法吗,这与建造者模式本身的理念是相反的,缺少了builder
这一层。
这种模式其实就是取消掉了director
这一层。将构建算法交给了client端,其次将builder 写到了要构建的产品类里面,最后采用了链式调用。
4.总结
使用建造者模式可以避免重叠构造函数的出现,你可以使用建造者模式创建不同形式的产品,也可以用来构造复杂对象。
建造者模式重点关注如何分步生成复杂对象。 抽象工厂专门用于生产一系列相关对象。 抽象工厂会马上返回产品, 生成器则允许你在获取产品前执行一些额外构造步骤。
相关参考文章:
blog.csdn.net/ShuSheng000…
refactoringguru.cn/design-patt…
juejin.cn/post/684490…