设计模式:构建者模式-示例二

目录

一、场景分析:一个复杂的订单对象

二、代码实战:实现订单对象的建造者模式

[1. 定义商品项和地址等辅助 DTO](#1. 定义商品项和地址等辅助 DTO)

[2. 实现核心的 OrderDTO 及其建造者](#2. 实现核心的 OrderDTO 及其建造者)

[3. 客户端如何使用](#3. 客户端如何使用)

三、在芋道(Yudao)框架中的实践要点

四、总结


在无人售货柜这类复杂的业务系统中,一个 "订单" 对象通常包含大量信息,如商品明细、支付信息、收货地址、物流信息、用户信息等。如果使用传统的构造器或大量的setter方法来创建,代码会非常臃肿且容易出错。

下面,我们将结合现代 Java 开发的最佳实践(如链式调用、final字段保证不可变性)和芋道(Yudao) 这类企业级开发框架的代码风格,为你展示如何为一个复杂的Order对象设计并实现建造者模式。

一、场景分析:一个复杂的订单对象

一个完整的订单OrderDTO(Data Transfer Object)可能包含以下部分:

  1. 基础信息:订单 ID、创建时间。
  2. 商品信息 :商品列表(List<OrderItemDTO>)。
  3. 支付信息:支付方式、支付金额、交易流水号。
  4. 用户信息:下单用户 ID。
  5. 收货信息:收货地址、联系人、联系电话。
  6. 物流信息:物流单号、物流公司。

二、代码实战:实现订单对象的建造者模式

我们将采用静态内部类的方式来实现建造者,这是目前最流行和推荐的做法,因为它将建造者与产品紧密耦合在一起,封装性最好。

1. 定义商品项和地址等辅助 DTO

首先,我们定义订单中会用到的子对象。

java

运行

复制代码
// OrderItemDTO.java
import lombok.Data;
import java.math.BigDecimal;

@Data
public class OrderItemDTO {
    private Long productId;
    private String productName;
    private Integer quantity;
    private BigDecimal unitPrice;
}

// AddressDTO.java
import lombok.Data;

@Data
public class AddressDTO {
    private String receiver;
    private String phone;
    private String province;
    private String city;
    private String district;
    private String detailAddress;
}
2. 实现核心的 OrderDTO 及其建造者

这是本次实战的核心。我们将建造者Builder作为OrderDTO的静态内部类,并使用链式调用。

java

运行

复制代码
// OrderDTO.java
import java.math.BigDecimal;
import java.time.LocalDateTime;
import java.util.ArrayList;
import java.util.List;
import java.util.Objects;

/**
 * 订单数据传输对象 (DTO)
 * 采用建造者模式构建,保证对象创建的灵活性和可读性
 */
public class OrderDTO {

    // === 基础信息 ===
    private final Long id;
    private final LocalDateTime createTime;

    // === 商品信息 ===
    private final List<OrderItemDTO> items;
    private final BigDecimal totalAmount;

    // === 支付信息 ===
    private final String paymentMethod;
    private final String transactionId;

    // === 用户信息 ===
    private final Long userId;

    // === 收货信息 ===
    private final AddressDTO deliveryAddress;

    // === 物流信息 ===
    private final String logisticsCode;
    private final String logisticsCompany;

    // 私有化构造器,只能通过Builder创建
    private OrderDTO(Builder builder) {
        this.id = builder.id;
        this.createTime = builder.createTime;
        this.items = new ArrayList<>(builder.items); // 深拷贝,防止外部修改
        this.totalAmount = calculateTotalAmount(builder.items); // 内部计算总金额
        this.paymentMethod = builder.paymentMethod;
        this.transactionId = builder.transactionId;
        this.userId = builder.userId;
        this.deliveryAddress = builder.deliveryAddress;
        this.logisticsCode = builder.logisticsCode;
        this.logisticsCompany = builder.logisticsCompany;

        // 在这里可以添加校验逻辑,确保订单的完整性
        validate();
    }

    /**
     * 计算订单总金额
     */
    private BigDecimal calculateTotalAmount(List<OrderItemDTO> items) {
        return items.stream()
                .map(item -> item.getUnitPrice().multiply(BigDecimal.valueOf(item.getQuantity())))
                .reduce(BigDecimal.ZERO, BigDecimal::add);
    }

    /**
     * 校验订单数据的完整性
     */
    private void validate() {
        Objects.requireNonNull(userId, "用户ID不能为空");
        Objects.requireNonNull(paymentMethod, "支付方式不能为空");
        Objects.requireNonNull(deliveryAddress, "收货地址不能为空");
        if (items == null || items.isEmpty()) {
            throw new IllegalStateException("订单商品列表不能为空");
        }
        if (totalAmount.compareTo(BigDecimal.ZERO) <= 0) {
            throw new IllegalStateException("订单总金额必须大于0");
        }
    }

    // Getters for all fields
    // ... (此处省略所有字段的getter方法,实际项目中需要生成)

    @Override
    public String toString() {
        return "OrderDTO{" +
                "id=" + id +
                ", createTime=" + createTime +
                ", items=" + items +
                ", totalAmount=" + totalAmount +
                ", paymentMethod='" + paymentMethod + '\'' +
                ", transactionId='" + transactionId + '\'' +
                ", userId=" + userId +
                ", deliveryAddress=" + deliveryAddress +
                ", logisticsCode='" + logisticsCode + '\'' +
                ", logisticsCompany='" + logisticsCompany + '\'' +
                '}';
    }

    /**
     * 静态内部建造者类
     */
    public static class Builder {
        // 1. 定义与OrderDTO完全相同的字段
        private Long id;
        private LocalDateTime createTime;
        private final List<OrderItemDTO> items = new ArrayList<>();
        private String paymentMethod;
        private String transactionId;
        private Long userId;
        private AddressDTO deliveryAddress;
        private String logisticsCode;
        private String logisticsCompany;

        // 2. 为每个字段提供链式的setter方法
        public Builder id(Long id) {
            this.id = id;
            return this;
        }

        public Builder createTime(LocalDateTime createTime) {
            this.createTime = createTime;
            return this;
        }

        public Builder addItem(OrderItemDTO item) {
            this.items.add(item);
            return this;
        }
        
        public Builder items(List<OrderItemDTO> items) {
            this.items.addAll(items);
            return this;
        }

        public Builder paymentMethod(String paymentMethod) {
            this.paymentMethod = paymentMethod;
            return this;
        }

        public Builder transactionId(String transactionId) {
            this.transactionId = transactionId;
            return this;
        }

        public Builder userId(Long userId) {
            this.userId = userId;
            return this;
        }

        public Builder deliveryAddress(AddressDTO deliveryAddress) {
            this.deliveryAddress = deliveryAddress;
            return this;
        }

        public Builder logisticsCode(String logisticsCode) {
            this.logisticsCode = logisticsCode;
            return this;
        }

        public Builder logisticsCompany(String logisticsCompany) {
            this.logisticsCompany = logisticsCompany;
            return this;
        }

        // 3. 提供一个build方法,创建OrderDTO实例
        public OrderDTO build() {
            // 可以在这里设置一些默认值
            if (this.createTime == null) {
                this.createTime = LocalDateTime.now();
            }
            return new OrderDTO(this);
        }
    }
}
3. 客户端如何使用

客户端代码变得极其清晰和易于阅读,就像在 "描述" 一个订单的创建过程。

java

运行

复制代码
// Client.java
import java.math.BigDecimal;
import java.time.LocalDateTime;
import java.util.Arrays;

public class Client {
    public static void main(String[] args) {
        // 1. 准备订单的各个组成部分
        OrderItemDTO item1 = new OrderItemDTO();
        item1.setProductId(1001L);
        item1.setProductName("可口可乐");
        item1.setQuantity(2);
        item1.setUnitPrice(new BigDecimal("3.5"));

        OrderItemDTO item2 = new OrderItemDTO();
        item2.setProductId(1002L);
        item2.setProductName("薯片");
        item2.setQuantity(1);
        item2.setUnitPrice(new BigDecimal("5.0"));

        AddressDTO address = new AddressDTO();
        address.setReceiver("张三");
        address.setPhone("13800138000");
        address.setProvince("广东省");
        address.setCity("深圳市");
        address.setDistrict("南山区");
        address.setDetailAddress("科技园路100号");

        // 2. 使用建造者模式创建订单
        OrderDTO order = new OrderDTO.Builder()
                .id(9527L)
                .userId(101L)
                .paymentMethod("WECHAT_PAY")
                .transactionId("wx20231027...")
                .deliveryAddress(address)
                .addItem(item1)
                .addItem(item2)
                .logisticsCompany("顺丰速运")
                .logisticsCode("SF1234567890")
                // createTime 会在build时自动填充为当前时间
                .build();

        // 3. 打印订单信息
        System.out.println("成功创建订单:");
        System.out.println(order);
    }
}

三、在芋道(Yudao)框架中的实践要点

芋道框架非常推崇领域驱动设计(DDD)和整洁的代码风格,上述建造者模式的实现方式与芋道的理念高度契合。在芋道项目中,你可以这样做:

  1. dto 包中定义 OrderDTO :将上述 OrderDTO.java 文件放在 your-project-module-api/src/main/java/.../dto/ 目录下。

  2. 结合 Lombok 简化代码 :虽然上面为了清晰展示原理而手动编写了构造器和getter,但在实际项目中,你可以使用 Lombok 的 @Getter@Builder 注解来极大地简化代码。

    java

    运行

    复制代码
    import lombok.Builder;
    import lombok.Getter;
    import lombok.Singular;
    // ... 其他 import
    
    @Getter
    @Builder(builderClassName = "Builder", toBuilder = true)
    public class OrderDTO {
        private final Long id;
        private final LocalDateTime createTime;
    
        @Singular // 用于集合,会自动生成 addItem 和 items 方法
        private final List<OrderItemDTO> items;
        
        private final BigDecimal totalAmount; // 注意:Lombok的@Builder无法直接处理派生字段,需要手动处理
        // ... 其他字段
    
        // 为了处理totalAmount这种派生字段,可以使用一个私有构造器和静态工厂方法
        // 或者在Service层计算好后再传入Builder
    }

    注意 :Lombok 的 @Builder 对于需要在构建时动态计算的字段(如 totalAmount)支持不佳。在这种情况下,手动实现建造者模式(如本示例所示)能提供更强大的控制力,因为你可以在 OrderDTO 的私有构造器中执行计算和校验逻辑。

  3. service 层中使用 :在你的 OrderService 实现类中,当需要创建一个 OrderDTO 返回给前端时,就可以使用这个建造者。

    java

    运行

    复制代码
    // OrderServiceImpl.java
    @Service
    public class OrderServiceImpl implements OrderService {
        @Override
        public OrderDTO getOrderDetails(Long orderId) {
            // ... 从数据库或领域模型中获取数据 ...
            OrderDO orderDO = orderMapper.selectById(orderId);
            List<OrderItemDO> itemDOs = orderItemMapper.selectListByOrderId(orderId);
            UserDO userDO = userMapper.selectById(orderDO.getUserId());
            
            // ... 将DO转换为DTO的各个部分 ...
            List<OrderItemDTO> itemDTOs = itemDOs.stream().map(...).collect(Collectors.toList());
            AddressDTO addressDTO = buildAddressDTO(userDO);
            
            // 使用建造者模式构建最终的OrderDTO
            return new OrderDTO.Builder()
                    .id(orderDO.getId())
                    .createTime(orderDO.getCreateTime())
                    .userId(orderDO.getUserId())
                    .paymentMethod(orderDO.getPaymentMethod())
                    .transactionId(orderDO.getTransactionId())
                    .deliveryAddress(addressDTO)
                    .items(itemDTOs)
                    .logisticsCompany(orderDO.getLogisticsCompany())
                    .logisticsCode(orderDO.getLogisticsCode())
                    .build();
        }
    }

四、总结

为订单对象实现建造者模式,带来了以下显著好处:

  • 可读性高:客户端代码如同自然语言,清晰地描述了订单的构成。
  • 灵活性强:可以轻松地增加或减少订单的组成部分,而无需修改构造函数。
  • 健壮性好 :通过在 build()OrderDTO 构造器中进行校验,可以确保创建出的订单对象总是有效的。
  • 代码解耦:将复杂对象的创建逻辑封装在建造者中,客户端无需关心对象的内部结构和创建细节。

这种模式非常适合在无人售货柜这类业务复杂、对象属性繁多的系统中广泛应用。

相关推荐
Coder_Boy_2 小时前
Spring AI 设计模式综合应用与完整工程实现
人工智能·spring·设计模式
有一个好名字2 小时前
设计模式-状态模式
设计模式·状态模式
爱学习的小可爱卢13 小时前
JavaEE进阶——Spring核心设计模式深度剖析
java·spring·设计模式
Geoking.1 天前
【设计模式】理解单例模式:从原理到最佳实践
单例模式·设计模式
阿闽ooo1 天前
桥接模式实战:用万能遥控器控制多品牌电视
c++·设计模式·桥接模式
驱动男孩1 天前
22种设计模式-个人理解
设计模式
__万波__1 天前
二十三种设计模式(十五)--访问者模式
java·设计模式·访问者模式
阿闽ooo2 天前
外观模式:从家庭电源控制看“简化接口“的设计智慧
c++·设计模式·外观模式
Geoking.2 天前
【UML】面向对象中类与类之间的关系详解
设计模式·uml