建造者模式:设计与实践

17 阅读13分钟

建造者模式:设计与实践

一、什么是建造者模式

1. 基本定义

建造者模式(Builder Pattern)是一种创建型设计模式,由《设计模式:可复用面向对象软件的基础》(GOF著作)定义为:将一个复杂对象的构建与它的表示分离,使得同样的构建过程可以创建不同的表示

该模式通过将复杂对象的创建过程拆分为多个可控步骤,允许开发者分步构建对象,并能在构建过程中进行校验和调整,最终生成符合预期的复杂对象。

2. 核心思想

建造者模式的核心在于分离"对象的构建过程"和"对象的表示形式"。通过引入专门的建造者角色负责对象的构建步骤,客户端只需指定所需对象的类型和内容,无需关心具体的构建细节,从而实现同一构建过程创建不同表现形式的对象。

二、建造者模式的特点

1. 分步构建复杂对象

将复杂对象的创建过程分解为一系列有序的构建步骤,每个步骤负责设置对象的一部分属性或组件,降低构建过程的复杂度。

2. 隔离构建与表示

对象的构建逻辑(如何创建)与对象的表示形式(创建成什么)相互隔离,相同的构建步骤可生成不同的对象实例。

3. 精确控制构建过程

客户端可以通过控制构建步骤的执行顺序和参数,精确控制对象的创建过程,满足不同场景的定制化需求。

4. 支持参数校验

在构建过程中或最终构建完成时,可对对象的属性进行集中校验,确保生成的对象符合业务规则,避免无效对象流转。

5. 链式调用优化

通常采用链式调用的方式设计建造者方法,使代码更具可读性,清晰表达对象的构建逻辑。

特点说明
分步构建将复杂对象创建拆分为有序步骤,降低复杂度
隔离构建与表示构建逻辑与对象表示分离,相同步骤可生成不同实例
精确控制客户端可控制步骤顺序和参数,满足定制需求
参数校验支持构建过程中的集中校验,确保对象有效性
链式调用采用链式API设计,提升代码可读性

三、建造者模式的标准代码实现

1. 模式结构

建造者模式包含四个核心角色:

  • 产品(Product):被构建的复杂对象,包含多个组件
  • 抽象建造者(Builder):定义构建产品的抽象方法和获取产品的方法
  • 具体建造者(ConcreteBuilder):实现抽象建造者的方法,完成具体构建步骤
  • 指挥者(Director):控制构建过程,按顺序调用建造者的方法

2. 代码实现示例

2.1 产品类(Product)
/**
 * 复杂产品:文档对象
 * 包含标题、内容段落、图片、签名等组件
 */
public class Document {
    private String title;
    private List<String> paragraphs = new ArrayList<>();
    private List<String> images = new ArrayList<>();
    private String signature;
    private String footer;

    // 产品的使用方法
    public void display() {
        System.out.println("标题:" + title);
        System.out.println("内容:");
        paragraphs.forEach(p -> System.out.println("- " + p));
        images.forEach(img -> System.out.println("图片:" + img));
        System.out.println("签名:" + signature);
        System.out.println("页脚:" + footer);
    }

    // 为建造者提供属性设置方法(通常为package-private或通过Builder内部类访问)
    void setTitle(String title) {
        this.title = title;
    }

    void addParagraph(String paragraph) {
        this.paragraphs.add(paragraph);
    }

    void addImage(String image) {
        this.images.add(image);
    }

    void setSignature(String signature) {
        this.signature = signature;
    }

    void setFooter(String footer) {
        this.footer = footer;
    }
}
2.2 抽象建造者(Builder)
/**
 * 文档建造者接口
 * 定义文档构建的步骤
 */
public interface DocumentBuilder {
    void buildTitle(String title);
    void buildParagraph(String content);
    void buildImage(String imagePath);
    void buildSignature(String signature);
    void buildFooter(String footer);
    Document getResult();
}
2.3 具体建造者(ConcreteBuilder)
/**
 * 报告文档建造者
 * 实现具体的构建逻辑
 */
public class ReportDocumentBuilder implements DocumentBuilder {
    private Document document = new Document();

    @Override
    public void buildTitle(String title) {
        // 报告标题格式处理
        document.setTitle("[报告] " + title);
    }

    @Override
    public void buildParagraph(String content) {
        // 报告段落格式处理
        document.addParagraph("■ " + content);
    }

    @Override
    public void buildImage(String imagePath) {
        // 报告图片格式处理
        document.addImage("报告附图:" + imagePath);
    }

    @Override
    public void buildSignature(String signature) {
        // 报告签名格式处理
        document.setSignature("报告人:" + signature);
    }

    @Override
    public void buildFooter(String footer) {
        // 报告页脚格式处理
        document.setFooter("报告生成日期:" + LocalDate.now() + " | " + footer);
    }

    @Override
    public Document getResult() {
        // 构建完成后进行校验
        if (document.getTitle() == null || document.getTitle().isEmpty()) {
            throw new IllegalStateException("报告标题不能为空");
        }
        if (document.getSignature() == null || document.getSignature().isEmpty()) {
            throw new IllegalStateException("报告必须包含签名");
        }
        return document;
    }
}
2.4 指挥者(Director)
/**
 * 文档构建指挥者
 * 控制文档的构建流程
 */
public class DocumentDirector {
    private DocumentBuilder builder;

    public DocumentDirector(DocumentBuilder builder) {
        this.builder = builder;
    }

    // 构建简单报告(标题+段落+签名)
    public Document buildSimpleReport(String title, List<String> contents, String signature) {
        builder.buildTitle(title);
        contents.forEach(content -> builder.buildParagraph(content));
        builder.buildSignature(signature);
        return builder.getResult();
    }

    // 构建完整报告(标题+段落+图片+签名+页脚)
    public Document buildFullReport(String title, List<String> contents, List<String> images, 
                                   String signature, String footer) {
        builder.buildTitle(title);
        contents.forEach(content -> builder.buildParagraph(content));
        images.forEach(image -> builder.buildImage(image));
        builder.buildSignature(signature);
        builder.buildFooter(footer);
        return builder.getResult();
    }
}
2.5 客户端使用
public class Client {
    public static void main(String[] args) {
        // 创建具体建造者
        DocumentBuilder builder = new ReportDocumentBuilder();
        // 创建指挥者
        DocumentDirector director = new DocumentDirector(builder);

        // 构建简单报告
        List<String> contents = Arrays.asList(
            "项目进度:已完成60%",
            "主要成果:完成核心模块开发",
            "下一步计划:进行系统测试"
        );
        Document simpleReport = director.buildSimpleReport("项目进展报告", contents, "张三");
        simpleReport.display();

        // 构建完整报告
        List<String> images = Arrays.asList("/images/progress.png", "/images/chart.png");
        Document fullReport = director.buildFullReport("项目验收报告", contents, images, "李四", "保密等级:内部");
        fullReport.display();
    }
}

3. 代码实现特点总结

角色核心职责代码特点
产品(Document)定义复杂对象的组成包含多个组件属性,提供使用方法
抽象建造者(DocumentBuilder)定义构建步骤接口声明所有可能的构建方法,返回产品的方法
具体建造者(ReportDocumentBuilder)实现构建步骤,完成实际构建持有产品实例,实现每个步骤的具体逻辑,提供校验
指挥者(DocumentDirector)控制构建流程封装构建算法,按固定顺序调用建造者方法

四、支付框架设计中建造者模式的运用

支付对账数据构建器为例,说明建造者模式在支付系统中的具体实现:

1. 场景分析

支付系统每日需与银行、第三方支付渠道进行对账,对账数据包含订单基本信息、交易流水、金额明细、状态标识、加密签名等多维度数据。不同渠道的对账格式(CSV/XML)、加密方式(AES/RSA)、字段规则存在差异,且需支持灵活扩展新渠道。

2. 设计实现

2.1 对账数据产品类
import java.math.BigDecimal;
import java.time.LocalDateTime;
import java.util.HashMap;
import java.util.Map;

/**
 * 支付对账数据(产品类)
 * 包含对账所需的核心信息
 */
public class ReconciliationData {
    // 基础信息
    private String orderId; // 订单号
    private String channelTxId; // 渠道交易号
    private String merchantId; // 商户号
    private BigDecimal amount; // 交易金额
    private String currency; // 币种
    private String transactionType; // 交易类型(支付/退款)
    private String status; // 状态(成功/失败)

    // 时间信息
    private LocalDateTime orderTime; // 下单时间
    private LocalDateTime payTime; // 支付时间
    private LocalDateTime reconciliationTime; // 对账时间

    // 安全信息
    private String encryptedData; // 加密数据
    private String signature; // 签名
    private String signType; // 签名类型

    // 扩展信息(渠道特有字段)
    private Map<String, String> extraFields = new HashMap<>();

    // 私有构造函数,仅允许建造者访问
    private ReconciliationData() {}

    // Getter方法(无Setter,确保对象创建后不可变)
    public String getOrderId() { return orderId; }
    public String getChannelTxId() { return channelTxId; }
    public String getMerchantId() { return merchantId; }
    public BigDecimal getAmount() { return amount; }
    public String getCurrency() { return currency; }
    public String getTransactionType() { return transactionType; }
    public String getStatus() { return status; }
    public LocalDateTime getOrderTime() { return orderTime; }
    public LocalDateTime getPayTime() { return payTime; }
    public LocalDateTime getReconciliationTime() { return reconciliationTime; }
    public String getEncryptedData() { return encryptedData; }
    public String getSignature() { return signature; }
    public String getSignType() { return signType; }
    public Map<String, String> getExtraFields() { return new HashMap<>(extraFields); }

    // 建造者内部类
    public static class Builder {
        private final ReconciliationData data = new ReconciliationData();

        // 基础信息设置(必填项)
        public Builder orderId(String orderId) {
            data.orderId = orderId;
            return this;
        }

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

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

        public Builder amount(BigDecimal amount) {
            data.amount = amount;
            return this;
        }

        // 可选信息设置
        public Builder currency(String currency) {
            data.currency = currency;
            return this;
        }

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

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

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

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

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

        // 安全相关设置
        public Builder encryptedData(String encryptedData) {
            data.encryptedData = encryptedData;
            return this;
        }

        public Builder signature(String signature, String signType) {
            data.signature = signature;
            data.signType = signType;
            return this;
        }

        // 扩展字段设置
        public Builder addExtraField(String key, String value) {
            data.extraFields.put(key, value);
            return this;
        }

        /**
         * 构建对账数据并校验
         */
        public ReconciliationData build() {
            // 基础校验
            if (data.orderId == null || data.orderId.isEmpty()) {
                throw new IllegalArgumentException("订单号不能为空");
            }
            if (data.channelTxId == null || data.channelTxId.isEmpty()) {
                throw new IllegalArgumentException("渠道交易号不能为空");
            }
            if (data.amount == null || data.amount.compareTo(BigDecimal.ZERO) <= 0) {
                throw new IllegalArgumentException("交易金额必须大于0");
            }
            if (data.reconciliationTime == null) {
                data.reconciliationTime = LocalDateTime.now(); // 默认当前时间
            }
            if (data.currency == null) {
                data.currency = "CNY"; // 默认人民币
            }
            // 安全校验(生产环境需严格校验)
            if (data.encryptedData == null && data.signature == null) {
                throw new SecurityException("对账数据必须包含加密信息或签名");
            }
            return data;
        }
    }
}
2.2 渠道对账建造者接口
import java.util.List;

/**
 * 对账数据建造者接口
 * 定义不同渠道对账数据的构建规范
 */
public interface ReconciliationBuilder {
    // 设置基础订单信息
    ReconciliationBuilder withOrderInfo(String orderId, String merchantId, BigDecimal amount);

    // 设置渠道交易信息
    ReconciliationBuilder withChannelInfo(String channelTxId, String transactionType, String status);

    // 设置时间信息
    ReconciliationBuilder withTimeInfo(LocalDateTime orderTime, LocalDateTime payTime);

    // 添加扩展字段
    ReconciliationBuilder addExtraFields(List<ExtraField> fields);

    // 加密处理
    ReconciliationBuilder encrypt(String secretKey);

    // 签名处理
    ReconciliationBuilder sign(String privateKey);

    // 构建对账数据
    ReconciliationData build();
}

/**
 * 扩展字段实体
 */
class ExtraField {
    private String key;
    private String value;

    public ExtraField(String key, String value) {
        this.key = key;
        this.value = value;
    }

    public String getKey() { return key; }
    public String getValue() { return value; }
}
2.3 具体渠道建造者(支付宝为例)
import javax.crypto.Cipher;
import javax.crypto.spec.SecretKeySpec;
import java.math.BigDecimal;
import java.nio.charset.StandardCharsets;
import java.security.MessageDigest;
import java.time.LocalDateTime;
import java.util.Base64;
import java.util.List;

/**
 * 支付宝对账数据建造者
 * 实现支付宝渠道的对账数据构建逻辑
 */
public class AlipayReconciliationBuilder implements ReconciliationBuilder {
    private final ReconciliationData.Builder dataBuilder = new ReconciliationData.Builder();

    @Override
    public ReconciliationBuilder withOrderInfo(String orderId, String merchantId, BigDecimal amount) {
        dataBuilder.orderId(orderId)
                  .merchantId(merchantId)
                  .amount(amount)
                  .currency("CNY"); // 支付宝默认人民币
        return this;
    }

    @Override
    public ReconciliationBuilder withChannelInfo(String channelTxId, String transactionType, String status) {
        dataBuilder.channelTxId(channelTxId)
                  .transactionType(transactionType)
                  .status(status)
                  // 支付宝特有扩展字段
                  .addExtraField("alipay_service", "alipay.trade.pay");
        return this;
    }

    @Override
    public ReconciliationBuilder withTimeInfo(LocalDateTime orderTime, LocalDateTime payTime) {
        dataBuilder.orderTime(orderTime)
                  .payTime(payTime);
        return this;
    }

    @Override
    public ReconciliationBuilder addExtraFields(List<ExtraField> fields) {
        fields.forEach(field -> dataBuilder.addExtraField(field.getKey(), field.getValue()));
        return this;
    }

    @Override
    public ReconciliationBuilder encrypt(String secretKey) {
        try {
            // 支付宝采用AES-128-CBC加密
            SecretKeySpec keySpec = new SecretKeySpec(secretKey.getBytes(StandardCharsets.UTF_8), "AES");
            Cipher cipher = Cipher.getInstance("AES/ECB/PKCS5Padding");
            cipher.init(Cipher.ENCRYPT_MODE, keySpec);

            // 待加密内容(实际场景需序列化关键信息)
            String content = "orderId=" + dataBuilder.build().getOrderId() + 
                            "&amount=" + dataBuilder.build().getAmount();
            byte[] encrypted = cipher.doFinal(content.getBytes(StandardCharsets.UTF_8));
            dataBuilder.encryptedData(Base64.getEncoder().encodeToString(encrypted));
        } catch (Exception e) {
            throw new SecurityException("支付宝对账数据加密失败", e);
        }
        return this;
    }

    @Override
    public ReconciliationBuilder sign(String privateKey) {
        try {
            // 支付宝采用RSA签名
            String content = "orderId=" + dataBuilder.build().getOrderId() +
                            "&channelTxId=" + dataBuilder.build().getChannelTxId() +
                            "&amount=" + dataBuilder.build().getAmount();
            String signature = RsaUtils.sign(content, privateKey, "UTF-8");
            dataBuilder.signature(signature, "RSA");
        } catch (Exception e) {
            throw new SecurityException("支付宝对账数据签名失败", e);
        }
        return this;
    }

    @Override
    public ReconciliationData build() {
        return dataBuilder.build();
    }
}
2.4 对账服务(指挥者)
/**
 * 对账服务(指挥者)
 * 控制对账数据的构建流程
 */
public class ReconciliationService {
    private final ReconciliationBuilder builder;

    // 注入具体建造者(依赖注入)
    public ReconciliationService(ReconciliationBuilder builder) {
        this.builder = builder;
    }

    /**
     * 构建标准对账数据
     */
    public ReconciliationData buildStandardData(String orderId, String merchantId, BigDecimal amount,
                                              String channelTxId, String status, LocalDateTime orderTime) {
        return builder.withOrderInfo(orderId, merchantId, amount)
                      .withChannelInfo(channelTxId, "PAY", status)
                      .withTimeInfo(orderTime, null)
                      .sign(getMerchantPrivateKey(merchantId)) // 签名
                      .build();
    }

    /**
     * 构建加密对账数据(高安全级别)
     */
    public ReconciliationData buildEncryptedData(String orderId, String merchantId, BigDecimal amount,
                                               String channelTxId, String status, List<ExtraField> extraFields) {
        return builder.withOrderInfo(orderId, merchantId, amount)
                      .withChannelInfo(channelTxId, "REFUND", status)
                      .withTimeInfo(null, LocalDateTime.now())
                      .addExtraFields(extraFields)
                      .encrypt(getChannelSecretKey()) // 加密
                      .sign(getMerchantPrivateKey(merchantId)) // 签名
                      .build();
    }

    // 获取商户私钥(实际从安全存储中获取)
    private String getMerchantPrivateKey(String merchantId) {
        return merchantKeyRepository.getPrivateKey(merchantId);
    }

    // 获取渠道密钥(实际从安全存储中获取)
    private String getChannelSecretKey() {
        return channelConfig.getSecretKey();
    }
}
2.5 客户端使用示例
public class ReconciliationClient {
    public static void main(String[] args) {
        // 支付宝对账场景
        ReconciliationBuilder alipayBuilder = new AlipayReconciliationBuilder();
        ReconciliationService service = new ReconciliationService(alipayBuilder);

        // 构建标准对账数据
        ReconciliationData standardData = service.buildStandardData(
            "ORD20230001", "MCH001", new BigDecimal("100.00"),
            "2023000001", "SUCCESS", LocalDateTime.of(2023, 10, 1, 10, 30)
        );

        // 构建加密对账数据(退款场景)
        List<ExtraField> extraFields = Arrays.asList(
            new ExtraField("refund_reason", "商品质量问题"),
            new ExtraField("refund_fee", "10.00")
        );
        ReconciliationData encryptedData = service.buildEncryptedData(
            "ORD20230002", "MCH001", new BigDecimal("10.00"),
            "2023000002", "SUCCESS", extraFields
        );

        // 提交对账
        reconciliationClient.submit(standardData);
        reconciliationClient.submit(encryptedData);
    }
}

3. 模式价值体现

  • 灵活性:新增支付渠道时,只需实现ReconciliationBuilder接口,无需修改对账服务核心逻辑
  • 可读性:链式调用清晰表达对账数据的构建过程,比传统setter方法更直观
  • 安全性:在build()方法中集中校验数据完整性和安全性,避免无效数据进入对账流程
  • 可维护性:将渠道特有逻辑(如加密、签名)封装在具体建造者中,职责单一,便于维护

五、开源框架中建造者模式的运用

MyBatis框架中的SqlSessionFactoryBuilder为例,说明建造者模式的框架级应用:

1. 核心实现分析

1.1 产品类与建造者

MyBatis中,SqlSessionFactory是核心产品类(用于创建SqlSession),SqlSessionFactoryBuilder是具体建造者,负责构建SqlSessionFactory

// 产品类:SqlSessionFactory(接口)
public interface SqlSessionFactory {
    SqlSession openSession();
    // 其他方法...
}

// 具体产品实现
public class DefaultSqlSessionFactory implements SqlSessionFactory {
    private final Configuration configuration;
    // 实现方法...
}

// 建造者类:SqlSessionFactoryBuilder
public class SqlSessionFactoryBuilder {
    // 构建SqlSessionFactory(核心建造方法)
    public SqlSessionFactory build(InputStream inputStream) {
        return build(inputStream, null, null);
    }

    public SqlSessionFactory build(InputStream inputStream, String environment, Properties properties) {
        try {
            // 解析配置文件(分步构建)
            XMLConfigBuilder parser = new XMLConfigBuilder(inputStream, environment, properties);
            // 构建并返回产品
            return build(parser.parse());
        } catch (Exception e) {
            // 异常处理...
        }
    }

    // 核心构建方法
    public SqlSessionFactory build(Configuration config) {
        return new DefaultSqlSessionFactory(config);
    }
}
1.2 构建流程
  1. 解析配置文件(XML/注解)
  2. 构建Configuration对象(包含数据源、映射器、插件等配置)
  3. 基于Configuration创建DefaultSqlSessionFactory
1.3 客户端使用
// 客户端代码
String resource = "mybatis-config.xml";
InputStream inputStream = Resources.getResourceAsStream(resource);
// 使用建造者构建SqlSessionFactory
SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(inputStream);
// 使用产品
SqlSession session = sqlSessionFactory.openSession();

2. 建造者模式在MyBatis中的价值

  • 分步构建复杂对象SqlSessionFactory依赖Configuration,而Configuration的构建需要解析数据源、映射文件、插件等,建造者模式将这一复杂过程拆分为多个步骤
  • 多源输入支持SqlSessionFactoryBuilder提供多种build()重载方法,支持从InputStreamReaderConfiguration等不同输入构建产品
  • 隔离构建细节:客户端无需关心Configuration的构建过程,只需通过建造者获取SqlSessionFactory
  • 灵活性:通过不同参数(如environment)可构建不同环境的SqlSessionFactory(开发/生产环境)

六、总结

1. 建造者模式的适用场景

  • 当对象包含多个组件,且构建过程需要多步骤时
  • 当需要创建不同表示的复杂对象(如不同格式的文档、不同渠道的对账数据)
  • 当对象创建需要严格的参数校验或安全处理时
  • 当希望构建过程具有良好的可读性和可维护性时

2. 与其他模式的区别

  • 与工厂模式:工厂模式侧重"创建什么",建造者模式侧重"如何创建";工厂模式适用于简单对象,建造者模式适用于复杂对象的分步构建
  • 与抽象工厂模式:抽象工厂模式创建产品族,建造者模式创建单个复杂产品;抽象工厂模式的产品之间相互关联,建造者模式的产品组件更灵活

3. 支付系统中的实践价值

  • 复杂对象标准化构建:对账数据、支付请求等复杂对象的构建过程标准化,减少人为错误
  • 多渠道适配:不同支付渠道的差异化处理(加密、格式)封装在具体建造者中,便于扩展
  • 安全性保障:在构建过程中集成签名、加密等安全校验,确保支付数据的完整性
  • 代码可读性提升:链式调用使参数设置逻辑清晰,降低后续维护成本

建造者模式通过分离构建与表示,为支付系统中复杂对象的创建提供了灵活、可控、可读的解决方案,是处理多参数、多步骤、多变化场景的理想选择。