设计模式手册006:建造者模式 - 复杂对象的优雅构建之道
本文是「设计模式手册」系列第006篇,我们将深入探讨建造者模式,这种模式将复杂对象的构建过程与其表示分离,使得同样的构建过程可以创建不同的表示。
1. 场景:我们为何需要建造者模式?
在软件开发中,我们经常遇到复杂对象的创建问题,这些对象通常由多个部分构成,且构建步骤复杂。例如:
- 电脑配置:由CPU、内存、硬盘、显卡等组成,配置灵活
- 订单信息:包含订单项、收货地址、支付方式、优惠券等
- 邮件消息:包含发件人、收件人、主题、正文、附件等
- 查询条件:包含多个过滤条件、排序规则、分页参数等
传统做法的困境:
java
public class Computer {
private String cpu;
private String memory;
private String hardDisk;
private String graphicsCard;
private String monitor;
// 更多组件...
// 方式1:巨型构造器
public Computer(String cpu, String memory, String hardDisk, String graphicsCard, String monitor, ...) {
this.cpu = cpu;
this.memory = memory;
this.hardDisk = hardDisk;
this.graphicsCard = graphicsCard;
this.monitor = monitor;
// ... 更多赋值
}
// 方式2:setter方法,但对象可能处于不完整状态
public Computer() {}
public void setCpu(String cpu) { this.cpu = cpu; }
public void setMemory(String memory) { this.memory = memory; }
public void setHardDisk(String hardDisk) { this.hardDisk = hardDisk; }
public void setGraphicsCard(String graphicsCard) { this.graphicsCard = graphicsCard; }
public void setMonitor(String monitor) { this.monitor = monitor; }
// ... 更多setter
}
// 使用方式1:构造器参数过多,难以阅读和维护
Computer computer = new Computer("Intel i7", "16GB", "1TB SSD", "NVIDIA GTX 3080", "27寸4K", ...);
// 使用方式2:setter方式,但可能忘记设置某些必要属性,导致对象不完整
Computer computer = new Computer();
computer.setCpu("Intel i7");
computer.setMemory("16GB");
// 忘记了设置硬盘,但程序不会立即报错,直到使用时报错
这种实现的痛点:
- 构造器参数过多,难以阅读和维护
- setter方式可能导致对象状态不一致
- 构建过程分散,无法保证构建的原子性
- 无法保证必填参数的完整性
2. 建造者模式:定义与本质
2.1 模式定义
建造者模式(Builder Pattern):将一个复杂对象的构建与它的表示分离,使得同样的构建过程可以创建不同的表示。
2.2 核心角色
java
// 产品类
public class Computer {
private String cpu;
private String memory;
private String hardDisk;
private String graphicsCard;
private String monitor;
// 更多组件...
// 私有构造器,只能通过建造者创建
private Computer(Builder builder) {
this.cpu = builder.cpu;
this.memory = builder.memory;
this.hardDisk = builder.hardDisk;
this.graphicsCard = builder.graphicsCard;
this.monitor = builder.monitor;
}
// 静态内部类建造者
public static class Builder {
// 必选参数
private String cpu;
private String memory;
private String hardDisk;
// 可选参数
private String graphicsCard;
private String monitor;
// 必选参数通过构造器传入
public Builder(String cpu, String memory, String hardDisk) {
this.cpu = cpu;
this.memory = memory;
this.hardDisk = hardDisk;
}
// 设置可选参数,返回建造者本身以支持链式调用
public Builder setGraphicsCard(String graphicsCard) {
this.graphicsCard = graphicsCard;
return this;
}
public Builder setMonitor(String monitor) {
this.monitor = monitor;
return this;
}
// 构建方法
public Computer build() {
// 可以在此处进行校验
if (cpu == null || memory == null || hardDisk == null) {
throw new IllegalArgumentException("必选参数不能为空");
}
return new Computer(this);
}
}
// getter方法
public String getCpu() { return cpu; }
public String getMemory() { return memory; }
public String getHardDisk() { return hardDisk; }
public String getGraphicsCard() { return graphicsCard; }
public String getMonitor() { return monitor; }
}
3. 深入理解:建造者模式的多维视角
3.1 第一重:分离构建与表示
核心思想:将复杂对象的构建过程封装在建造者中,产品类只关注自身的表示。
java
// 使用建造者模式
public class ComputerShop {
public static void main(String[] args) {
// 链式调用,清晰明了
Computer gamingComputer = new Computer.Builder("Intel i9", "32GB", "2TB SSD")
.setGraphicsCard("NVIDIA RTX 4090")
.setMonitor("32寸4K 144Hz")
.build();
Computer officeComputer = new Computer.Builder("Intel i5", "16GB", "512GB SSD")
.setMonitor("24寸1080P")
.build();
// 可以确保构建的对象是完整的
System.out.println("游戏电脑配置: " + gamingComputer.getCpu() + ", " + gamingComputer.getGraphicsCard());
System.out.println("办公电脑配置: " + officeComputer.getCpu() + ", " + officeComputer.getGraphicsCard());
}
}
3.2 第二重:构建过程的控制
建造者可以控制构建步骤,确保构建过程的正确性。
java
public class Order {
private final String orderId;
private final List<OrderItem> items;
private final String shippingAddress;
private final String paymentMethod;
private final BigDecimal discount;
private Order(OrderBuilder builder) {
this.orderId = builder.orderId;
this.items = builder.items;
this.shippingAddress = builder.shippingAddress;
this.paymentMethod = builder.paymentMethod;
this.discount = builder.discount;
}
public static class OrderBuilder {
private String orderId;
private List<OrderItem> items;
private String shippingAddress;
private String paymentMethod;
private BigDecimal discount;
public OrderBuilder(String orderId) {
this.orderId = orderId;
this.items = new ArrayList<>();
}
public OrderBuilder addItem(OrderItem item) {
this.items.add(item);
return this;
}
public OrderBuilder setShippingAddress(String address) {
this.shippingAddress = address;
return this;
}
public OrderBuilder setPaymentMethod(String method) {
this.paymentMethod = method;
return this;
}
public OrderBuilder setDiscount(BigDecimal discount) {
this.discount = discount;
return this;
}
public Order build() {
// 构建前的校验
if (items.isEmpty()) {
throw new IllegalStateException("订单必须包含至少一个商品");
}
if (shippingAddress == null) {
throw new IllegalStateException("收货地址不能为空");
}
if (paymentMethod == null) {
throw new IllegalStateException("支付方式不能为空");
}
return new Order(this);
}
}
}
3.3 第三重:与工厂模式的区别
- 工厂模式:关注整体对象的创建,创建过程相对固定
- 建造者模式:关注复杂对象的逐步构建,可以自定义构建过程
4. 实战案例:完整的订单构建系统
java
// 订单项
public class OrderItem {
private final String productId;
private final String productName;
private final int quantity;
private final BigDecimal price;
public OrderItem(String productId, String productName, int quantity, BigDecimal price) {
this.productId = productId;
this.productName = productName;
this.quantity = quantity;
this.price = price;
}
// getter方法
public String getProductId() { return productId; }
public String getProductName() { return productName; }
public int getQuantity() { return quantity; }
public BigDecimal getPrice() { return price; }
public BigDecimal getTotalPrice() { return price.multiply(BigDecimal.valueOf(quantity)); }
}
// 订单类
public class Order {
private final String orderId;
private final List<OrderItem> items;
private final String customerName;
private final String shippingAddress;
private final String paymentMethod;
private final BigDecimal discount;
private final LocalDateTime orderTime;
private Order(OrderBuilder builder) {
this.orderId = builder.orderId;
this.items = Collections.unmodifiableList(builder.items);
this.customerName = builder.customerName;
this.shippingAddress = builder.shippingAddress;
this.paymentMethod = builder.paymentMethod;
this.discount = builder.discount;
this.orderTime = builder.orderTime;
}
public static class OrderBuilder {
private String orderId;
private List<OrderItem> items;
private String customerName;
private String shippingAddress;
private String paymentMethod;
private BigDecimal discount;
private LocalDateTime orderTime;
public OrderBuilder(String orderId, String customerName) {
this.orderId = orderId;
this.customerName = customerName;
this.items = new ArrayList<>();
this.orderTime = LocalDateTime.now();
}
public OrderBuilder addItem(OrderItem item) {
this.items.add(item);
return this;
}
public OrderBuilder setShippingAddress(String shippingAddress) {
this.shippingAddress = shippingAddress;
return this;
}
public OrderBuilder setPaymentMethod(String paymentMethod) {
this.paymentMethod = paymentMethod;
return this;
}
public OrderBuilder setDiscount(BigDecimal discount) {
this.discount = discount;
return this;
}
public OrderBuilder setOrderTime(LocalDateTime orderTime) {
this.orderTime = orderTime;
return this;
}
public Order build() {
// 构建前的校验
if (items.isEmpty()) {
throw new IllegalStateException("订单必须包含至少一个商品");
}
if (shippingAddress == null) {
throw new IllegalStateException("收货地址不能为空");
}
if (paymentMethod == null) {
throw new IllegalStateException("支付方式不能为空");
}
return new Order(this);
}
}
// getter方法
public String getOrderId() { return orderId; }
public List<OrderItem> getItems() { return items; }
public String getCustomerName() { return customerName; }
public String getShippingAddress() { return shippingAddress; }
public String getPaymentMethod() { return paymentMethod; }
public BigDecimal getDiscount() { return discount; }
public LocalDateTime getOrderTime() { return orderTime; }
public BigDecimal getTotalAmount() {
BigDecimal total = items.stream()
.map(OrderItem::getTotalPrice)
.reduce(BigDecimal.ZERO, BigDecimal::add);
if (discount != null) {
total = total.subtract(discount);
}
return total;
}
}
// 使用示例
public class OrderSystem {
public static void main(String[] args) {
Order order = new Order.OrderBuilder("ORDER001", "张三")
.addItem(new OrderItem("P001", "iPhone 14", 1, new BigDecimal("5999")))
.addItem(new OrderItem("P002", "AirPods", 1, new BigDecimal("1299")))
.setShippingAddress("北京市海淀区xxx街道")
.setPaymentMethod("支付宝")
.setDiscount(new BigDecimal("100"))
.build();
System.out.println("订单ID: " + order.getOrderId());
System.out.println("客户: " + order.getCustomerName());
System.out.println("总金额: " + order.getTotalAmount());
System.out.println("订单时间: " + order.getOrderTime());
}
}
5. Lombok的@Builder注解
在实际开发中,我们可以使用Lombok来简化建造者模式的实现:
java
import lombok.Builder;
import lombok.Value;
@Value
@Builder
public class Computer {
private String cpu;
private String memory;
private String hardDisk;
@Builder.Default private String graphicsCard = "集成显卡";
private String monitor;
}
// 使用Lombok生成的建造者
public class LombokBuilderExample {
public static void main(String[] args) {
Computer computer = Computer.builder()
.cpu("Intel i7")
.memory("16GB")
.hardDisk("1TB SSD")
.graphicsCard("NVIDIA GTX 3080")
.monitor("27寸4K")
.build();
System.out.println(computer.getCpu());
System.out.println(computer.getGraphicsCard());
}
}
6. 建造者模式的进阶用法
6.1 导演类(Director)
当构建过程非常复杂且固定时,可以引入导演类来封装构建过程:
java
// 导演类
public class ComputerDirector {
private Computer.Builder builder;
public ComputerDirector(Computer.Builder builder) {
this.builder = builder;
}
public Computer constructGamingComputer() {
return builder.setGraphicsCard("NVIDIA RTX 4090")
.setMonitor("32寸4K 144Hz")
.build();
}
public Computer constructOfficeComputer() {
return builder.setGraphicsCard("集成显卡")
.setMonitor("24寸1080P")
.build();
}
public Computer constructDevelopmentComputer() {
return builder.setGraphicsCard("NVIDIA RTX 3080")
.setMonitor("27寸4K")
.build();
}
}
// 使用导演类
public class DirectorExample {
public static void main(String[] args) {
Computer.Builder builder = new Computer.Builder("Intel i7", "16GB", "1TB SSD");
ComputerDirector director = new ComputerDirector(builder);
Computer gamingComputer = director.constructGamingComputer();
Computer officeComputer = director.constructOfficeComputer();
System.out.println("游戏电脑: " + gamingComputer.getGraphicsCard());
System.out.println("办公电脑: " + officeComputer.getGraphicsCard());
}
}
6.2 泛型建造者
java
// 泛型建造者接口
public interface Builder<T> {
T build();
}
// 具体建造者实现
public class ComputerBuilder implements Builder<Computer> {
private String cpu;
private String memory;
private String hardDisk;
private String graphicsCard;
private String monitor;
public ComputerBuilder setCpu(String cpu) {
this.cpu = cpu;
return this;
}
public ComputerBuilder setMemory(String memory) {
this.memory = memory;
return this;
}
public ComputerBuilder setHardDisk(String hardDisk) {
this.hardDisk = hardDisk;
return this;
}
public ComputerBuilder setGraphicsCard(String graphicsCard) {
this.graphicsCard = graphicsCard;
return this;
}
public ComputerBuilder setMonitor(String monitor) {
this.monitor = monitor;
return this;
}
@Override
public Computer build() {
// 校验和构建逻辑
return new Computer(cpu, memory, hardDisk, graphicsCard, monitor);
}
}
7. 建造者模式 vs 其他模式
7.1 建造者模式 vs 工厂模式
- 工厂模式:关注整体对象的创建,创建过程相对固定
- 建造者模式:关注复杂对象的逐步构建,可以自定义构建过程
7.2 建造者模式 vs 原型模式
- 建造者模式:通过一步步构建来创建新对象
- 原型模式:通过复制现有对象来创建新对象
7.3 建造者模式 vs 策略模式
- 建造者模式:关注对象的构建过程
- 策略模式:关注算法的选择和替换
8. 总结与思考
8.1 建造者模式的优点
- 封装性好:构建过程与产品表示分离
- 扩展性好:不同的建造者可以创建不同的产品
- 控制构建过程:可以精细控制构建步骤
- 避免重叠构造器:解决多个构造参数的问题
- 保证产品完整性:可以在build方法中校验
8.2 建造者模式的缺点
- 代码重复:产品类和建造者类的代码相似
- 增加复杂性:需要创建多个类
- 性能影响:对象创建需要多步,可能影响性能
8.3 设计思考
建造者模式的本质是**"分步骤构建复杂对象"**。它将复杂对象的构建过程分解为多个简单的步骤,使得构建过程更加清晰和可控。
深入思考的角度:
"建造者模式通过将构建过程分解为多个步骤,使得我们可以更加精细地控制对象的创建。它不仅解决了'重叠构造器'的问题,还提供了更好的可读性和灵活性。"
在实际应用中,建造者模式有很多优秀的实践:
- Java中的StringBuilder和StringBuffer
- Spring中的BeanDefinitionBuilder
- Jackson中的JsonNodeBuilder
- 各种查询条件的构建
从系统设计的角度看,建造者模式特别适合构建那些包含多个组成部分的复杂对象,尤其是当这些组成部分的构建顺序或组合方式可能变化时。
使用场景判断:
- 适合:对象有多个组成部分,构建过程复杂,需要不同的构建顺序或组合
- 不适合:对象构建简单,或者构建过程固定不变
下一篇预告:设计模式手册007 - 原型模式:如何通过复制现有对象来创建新对象?
版权声明:本文为CSDN博主原创文章,遵循CC 4.0 BY-SA版权协议,转载请附上原文出处链接及本声明。