掌握设计模式之生成器模式
本文的主要内容有:
生成器模式(Builder Pattern)
意图
将复杂对象的构造与其表示分开,使得相同的构造过程可以创建不同的表示。
适用性
适用于需要创建复杂对象,对象的构建过程比较灵活,可以通过多个步骤逐步完成。
案例一:参数传递方式
案例概述
使用多个零部件组装成一台的手机,不同型号或品牌的手机由不同的零部件构成。
案例类图
案例代码
// 手机产品
class Phone {
private Screen screen;
private Battery battery;
private Cpu cpu;
private Camera camera;
// 打印产品详情信息
public String getSpecification() {
return new StringBuilder("手机规格:\n")
.append(" - ").append(this.screen).append("\n")
.append(" - ").append(this.battery).append("\n")
.append(" - ").append(this.cpu).append("\n")
.append(" - ").append(this.camera).append("\n")
.append("\n").toString();
}
public void setScreen(Screen screen) {
this.screen = screen;
}
public void setBattery(Battery battery) {
this.battery = battery;
}
public void setCpu(Cpu cpu) {
this.cpu = cpu;
}
public void setCamera(Camera camera) {
this.camera = camera;
}
}
// 具体零部件--显示屏
class Screen {
private String name;
private String distinguishability;
public Screen(String name, String distinguishability) {
this.name = name;
this.distinguishability = distinguishability;
}
@Override
public String toString() {
final StringBuffer sb = new StringBuffer("Screen{");
sb.append("name='").append(name).append('\'');
sb.append(", distinguishability='").append(distinguishability).append('\'');
sb.append('}');
return sb.toString();
}
// 省略get set...
}
// 具体零部件--电池
class Battery{
private String name;
private String capacity;
public Battery(String name, String capacity) {
this.name = name;
this.capacity = capacity;
}
@Override
public String toString() {
final StringBuffer sb = new StringBuffer("Battery{");
sb.append("name='").append(name).append('\'');
sb.append(", capacity='").append(capacity).append('\'');
sb.append('}');
return sb.toString();
}
// 省略get set...
}
// 具体零部件--芯片
class Cpu{
private String name;
private String version;
public Cpu(String name, String version) {
this.name = name;
this.version = version;
}
@Override
public String toString() {
final StringBuffer sb = new StringBuffer("Cpu{");
sb.append("name='").append(name).append('\'');
sb.append(", version='").append(version).append('\'');
sb.append('}');
return sb.toString();
}
// 省略get set...
}
// 具体零部件--摄像头
class Camera{
private String name;
private String pixel;
public Camera(String name, String pixel) {
this.name = name;
this.pixel = pixel;
}
@Override
public String toString() {
final StringBuffer sb = new StringBuffer("Camera{");
sb.append("name='").append(name).append('\'');
sb.append(", pixel='").append(pixel).append('\'');
sb.append('}');
return sb.toString();
}
// 省略get set...
}
// 生成器接口
interface PhoneBuilder {
PhoneBuilder buildScreen(Screen screen);
PhoneBuilder buildBattery(Battery battery);
PhoneBuilder buildCpu(Cpu cpu);
PhoneBuilder buildCamera(Camera camera);
Phone getPhone();
}
// 具体生成器
class ConcretePhoneBuilder implements PhoneBuilder {
private Phone phone = new Phone();
@Override
public PhoneBuilder buildScreen(Screen screen) {
phone.setScreen(screen);
return this;
}
@Override
public PhoneBuilder buildBattery(Battery battery) {
phone.setBattery(battery);
return this;
}
@Override
public PhoneBuilder buildCpu(Cpu cpu) {
phone.setCpu(cpu);
return this;
}
@Override
public PhoneBuilder buildCamera(Camera camera) {
phone.setCamera(camera);
return this;
}
@Override
public Phone getPhone() {
return phone;
}
}
// Director 类
class Director {
// 构建手机的方法
public static void construct(PhoneBuilder phoneBuilder) {
phoneBuilder.buildScreen(new Screen("三星屏幕","2k分辨率"))
.buildBattery(new Battery("国产电池","5400毫安"))
.buildCpu(new Cpu("骁龙CPU", "骁龙8+"))
.buildCamera(new Camera("徕卡摄像头","一亿像素"));
}
public static void constructXl(PhoneBuilder phoneBuilder) {
phoneBuilder.buildScreen(new Screen("国产屏幕","2k分辨率"))
.buildBattery(new Battery("国产电池","4800毫安"))
.buildCpu(new Cpu("麒麟CPU","9000"))
.buildCamera(new Camera("徕卡摄像头","两亿像素"));
}
}
// 测试类
public class BuilderDemo {
public static void main(String[] args) {
PhoneBuilder phoneBuilder = new ConcretePhoneBuilder();
Director.construct(phoneBuilder);//执行构建:该产品的生产过程
Phone phone = phoneBuilder.getPhone();// 获取构建结果:获取产品
System.out.println(phone.getSpecification());
Director.constructXl(phoneBuilder);
Phone phone1 = phoneBuilder.getPhone();
System.out.println(phone1.getSpecification());
}
}
案例简析
Director
类通过生成器传参的方式
,调用具体生成器ConcretePhoneBuilder
类的方法来构建一个复杂的对象Phone
。这种传参的生成器方式其实可以不需要PhoneBuilder
接口,直接使用具体生成器ConcretePhoneBuilder
即可。
这种生成器方式在Spring、SpringSecurity、MyBatis等框架中经常用到,比如
SpringSecurity下的身份验证管理器生成器
AuthenticationManagerBuilder类
Spring下的Bean定义构建器
BeanDefinitionBuilder类
还有一种通过编码方式来实现生成器。
案例二:编码的方式
案例概述
使用相同的构建过程,生成两款不同配置的电脑。
案例类图
案例代码
// 产品:电脑
class Computer {
private String CPU;
private String GPU;
private int RAM;
private int storage;
public void setCPU(String CPU) {
this.CPU = CPU;
}
public void setGPU(String GPU) {
this.GPU = GPU;
}
public void setRAM(int RAM) {
this.RAM = RAM;
}
public void setStorage(int storage) {
this.storage = storage;
}
@Override
public String toString() {
return "Computer{" +
"CPU='" + CPU + '\'' +
", GPU='" + GPU + '\'' +
", RAM=" + RAM +
", storage=" + storage +
'}';
}
}
// 抽象生成器
interface ComputerBuilder {
void buildCPU();
void buildGPU();
void buildRAM();
void buildStorage();
Computer getComputer();
}
// 具体生成器:高端电脑生成器
class HighEndComputerBuilder implements ComputerBuilder {
private Computer computer;
public HighEndComputerBuilder() {
this.computer = new Computer();
}
@Override
public void buildCPU() {
computer.setCPU("高端电脑 CPU");
}
@Override
public void buildGPU() {
computer.setGPU("高端电脑 GPU");
}
@Override
public void buildRAM() {
computer.setRAM(32);
}
@Override
public void buildStorage() {
computer.setStorage(1000);
}
@Override
public Computer getComputer() {
return computer;
}
}
// 具体生成器:标准电脑生成器
class StandardComputerBuilder implements ComputerBuilder {
private Computer computer;
public StandardComputerBuilder() {
this.computer = new Computer();
}
@Override
public void buildCPU() {
computer.setCPU("标准电脑 CPU");
}
@Override
public void buildGPU() {
computer.setGPU("标准电脑 GPU");
}
@Override
public void buildRAM() {
computer.setRAM(8);
}
@Override
public void buildStorage() {
computer.setStorage(500);
}
@Override
public Computer getComputer() {
return computer;
}
}
// Director 类
class ComputerDirector {
private ComputerBuilder computerBuilder;
public ComputerDirector(ComputerBuilder computerBuilder) {
this.computerBuilder = computerBuilder;
}
// 统一的构建过程
public void buildComputer() {
computerBuilder.buildCPU();
computerBuilder.buildGPU();
computerBuilder.buildRAM();
computerBuilder.buildStorage();
}
public Computer getComputer() {
return computerBuilder.getComputer();
}
}
// 客户端代码
public class BuilderDemo2 {
public static void main(String[] args) {
// 构建高端电脑
ComputerBuilder highEndBuilder = new HighEndComputerBuilder();
ComputerDirector highEndDirector = new ComputerDirector(highEndBuilder);
highEndDirector.buildComputer();
Computer highEndComputer = highEndDirector.getComputer();
System.out.println("高端电脑: " + highEndComputer);
// 构建标准电脑
ComputerBuilder standardBuilder = new StandardComputerBuilder();
ComputerDirector standardDirector = new ComputerDirector(standardBuilder);
standardDirector.buildComputer();
Computer standardComputer = standardDirector.getComputer();
System.out.println("标准电脑: " + standardComputer);
}
}
案例简析
HighEndComputerBuilder
和StandardComputerBuilder
生成器实现了ComputerBuilder
接口,并在具体生成器中使用硬编码的方式编写好了每一步。ComputerDirector
类使用统一的构建过程buildComputer()
来生产不同的复杂对象Computer
。
这种生成器方式在Spring、MyBatis等框架中也经常用到,比如:MyBatis 通过解析XML文件配置来构建对象的XMLConfigBuilder
类
注意,同一个构建过程,根据不同的生成器生成不同的复杂对象信息。在新增了新的生成器时,这个构建过程是不需要修改的,这就区别于案例一的参数传递的方式。这里的硬编码也可以通过获取配置文件内容
或读取数据库数据
来赋值。
总结
生成器的重点关注如何分步生成复杂对象
,其生成方式并不固定必须怎么做,向符合设计原则
和解决设计问题
的方向靠即可。
以上两种方式之间的主要区别在于:
-
硬编码的方式: 往往用于通过配置文件生成复杂对象,一般有生成器接口
- 在具体生成器类中"硬编码"构建过程的每个步骤,每个具体生成器都有其独特的构建逻辑。
- 适用于每个具体生成器都有不同构建过程的情况。
具体示例可阅读MyBatis 的抽象接口
BaseBuilder
及其具体生成器类。比如,XMLConfigBuilder
类用于解析xml配置文件生成Configuration
对象并注册各种配置到Configuration
对象中和解析环境配置等。而其他的生成器则实现了其独特的构建逻辑。 -
传递参数的方式: 代码传参自定义生成复杂对象,一般无需抽象接口
- 通过在构造函数中传递参数,使得构建逻辑变得更加灵活,可以动态指定要构建的对象的属性。
- 适用于多个生成器共享相似构建逻辑,但具体参数可能有所不同。
具体示例可阅读Spring的
BeanDefinitionBuilder
类。BeanDefinitionBuilder
类提供了一种流式的、更简洁的方式来构建BeanDefinition
对象,该方式由调用者传参来决定生成怎样的BeanDefinition
对象。
选择使用哪种方式取决于具体的需求。如果每个具体生成器都有自己独特的构建逻辑,“硬编码的方式”可能更为合适。如果多个生成器之间有相似的构建逻辑,但具体参数不同,传递参数的方式可能更为灵活。