建造者模式
前言
在正式学习建造者模式之前,我一直把那种链式编程当成是建造者模式,类似obj.set(“A”).set(“B”).set(“C”).build();这种,后来认真的去研究了一下,发现并不是我想象的那样,怎么说呢,对但不完全对。为了真正搞懂建造者模式,也是查阅了大量的资料,发现网上包括很多视频也是讲的比较片面,有的甚至是错的,很容易误导初学者,所以特此记录一下。
建造者模式
基本概念
建造者模式
(Builder Pattern)将一个复杂对象的构建和它的表示分离,使同样的构建过程可以创建不同的表示。
不知道各位看这个概念明白没有,反正我第一次是没看懂,看图解释一下吧。
上图左边是电脑的各个部件,包括CPU、主板、硬盘、内存条,右边是完整的主机。主机就可以看作是复杂的对象,我们组装电脑的过程就叫复杂对象的构建,分离其实就是解耦合;组装电脑的时候可以使用不同型号的部件去组装,比如CPU有Intel和AMD,主板有华硕和联想等等,虽然最后组装好的产品都是主机,但是主机的性能肯定是不一样的,这就叫同样的构建过程可以创建不同的表示。
结构
建造者模式包含如下角色:
Product
(产品):具体产品对象Builder
(抽象建造者):创建一个产品各个部件的接口ConcreteBuilder
(具体建造者):实现抽象建造者对应的接口Director
(指挥者):创建一个复杂的对象,控制具体的流程
代码实现
- 先创建一个产品类
public class Computer {
private String board; // 主板
private String cpu; // cpu
private String disk; // 硬盘
private String memory; // 内存条
// 省略get/set/toString方法
}
- 创建一个抽象建造者类
public abstract class ComputerBuilder {
// 构建主机的抽象部件
public abstract void board();
public abstract void cpu();
public abstract void disk();
public abstract void memory();
// 构建主机的抽象方法
public abstract Computer buildComputer();
}
- 有了抽象类,就得有对应的实现,创建一个具体建造者实现类
public class LenovoComputer extends ComputerBuilder{
// 具体建造者创建产品
private Computer computer = new Computer();
@Override
public void board(String board) {
computer.setBoard(board);
}
@Override
public void cpu(String cpu) {
computer.setCpu(cpu);
}
@Override
public void disk(String disk) {
computer.setDisk(disk);
}
@Override
public void memory(String memory) {
computer.setMemory(memory);
}
@Override
public Computer buildComputer() {
return computer;
}
}
- 最后创建一个指挥者类,Director类的主要作用是调用具体的Builder,来构建对象的各个部分,Director类起到封装作用,避免高层模块深入到建造者内部的实现类。
public class Director {
private ComputerBuilder computerBuilder;
public Director(ComputerBuilder computerBuilder) {
this.computerBuilder = computerBuilder;
}
// 指挥者设置流程的先后顺序,返回具体产品
public Computer build(String board, String cpu, String disk, String memory){
computerBuilder.board(board);
computerBuilder.cpu(cpu);
computerBuilder.disk(disk);
computerBuilder.memory(memory);
return computerBuilder.buildComputer();
}
}
- 客户端验证
public class Client {
public static void main(String[] args) {
// 具体的建造者
ComputerBuilder builder = new LenovoComputer();
// 指挥者
Director director = new Director(builder);
// 得到具体的产品
Computer computer = director.build("联想主板", "Intel处理器", "联想硬盘", "DDR3内存条");
System.out.println(computer.toString());
}
}
输出结果:
Computer{board='联想主板', cpu='Intel处理器', disk='联想硬盘', memory='DDR3内存条'}
从上面的代码可以看到,Computer产品是通过LenovoComputer构建的,Director封装了构建复杂产品对象的过程,对外隐藏了构建细节。ComputerBuilder和Director一起将一个复杂的对象的构建与表示分离。
建造者模式的优缺点
优点:
- 隐藏了产品创建的细节
客户端不必知道产品内部细节,将产品本身与产品创建过程解耦,使得相同的创建过程可以创建出不同的产品对象。
- 可以更加精细地控制产品的创建过程
将复杂产品的创建步骤分解在不同的方法中,使得创建过程更加清晰,也更方便使用程序来控制创建过程。
- 容易扩展
如果有新需求,通过实现一个新的建造者类就可以完成,基本上不用修改之前的代码,符合开闭原则。
缺点:
建造者模式所创建的产品一般具有较多的共同点,其组成部分相似,如果产品之间差异很大,则不适合使用建造者模式,因此其使用范围受限制。
使用场景
建造者模式创建的是复杂的对象,其产品的各个部分经常面临着剧烈的变化,但将它们组合在一起的算法却相对稳定,所以它通常在以下场合使用。
- 创建的对象较复杂,由多个部件构成,各部件面临着复杂的变化,但部件间的建造顺序是稳定的;
- 创建复杂对象的算法独立于该对象的组成部分以及它们的装配方式,即产品的构建过程和最终的表示是独立的。
模式扩展
建造者模式除了上面的用途外,在开发中还有一个常用的使用方式,就是当一个类构造器需要传入很多参数时,如果创建这个类的实例,代码可读性会非常差,而且很容易引入错误,此时就可以利用建造者模式进行重构。
重构前代码如下:
public class MacBook {
private String board; // 主板
private String cpu; // cpu
private String disk; // 硬盘
private String memory; // 内存条
public MacBook(String board, String cpu, String disk, String memory) {
this.board = board;
this.cpu = cpu;
this.disk = disk;
this.memory = memory;
}
// 省略get/set/toString方法
}
public class Client {
public static void main(String[] args) {
// 重构前
MacBook macBook = new MacBook("苹果主板","M1处理器", "苹果硬盘","苹果内存条");
System.out.println(macBook.toString());
}
}
上面在客户端代码中构建MacBook对象,传递了四个参数,如果参数更多呢?代码的可读性及使用成本就会很高。
重构后代码:
public class MacBook {
private String board; // 主板
private String cpu; // cpu
private String disk; // 硬盘
private String memory; // 内存条
private MacBook(Builder builder) {
this.board = builder.board;
this.cpu = builder.cpu;
this.disk = builder.disk;
this.memory = builder.memory;
}
public static class Builder {
private String board;
private String cpu;
private String disk;
private String memory;
public Builder setBoard(String board) {
this.board = board;
return this;
}
public Builder setCpu(String cpu) {
this.cpu = cpu;
return this;
}
public Builder setDisk(String disk) {
this.disk = disk;
return this;
}
public Builder setMemory(String memory) {
this.memory = memory;
return this;
}
public MacBook build() {
return new MacBook(this);
}
}
// 省略toString方法
}
客户端验证:
public class Client {
public static void main(String[] args) {
MacBook macBook = new MacBook.Builder()
.setBoard("苹果主板")
.setCpu("M1处理器")
.setDisk("苹果硬盘")
.setMemory("苹果内存条")
.build();
System.out.println(macBook.toString());
}
}
输出结果:
MacBook{board='苹果主板', cpu='M1处理器', disk='苹果硬盘', memory='苹果内存条'}
重构后的代码在使用起来更方便,某种程度上也可以提高开发效率。
这就是我开头提到的链式编程,当初一直把这个误以为就是建造者模式。一定要分清楚,这只是建造者模式的一种扩展用法,标准的写法是最开始那种。
有态度的总结
建造者模式可以将构建和装配解耦,一步一步创建一个复杂的对象,它也属于创建型模式;
工作中如果构造对象的参数特别多,常使用建造者模式对构造函数进行优化。
OK,建造者模式讲完了,我们下期见~
本博客所有文章均为原创,转载请注明出处!