文章中如果有图看不到,可以点这里去 csdn 看看。从那边导过来的,文章太多,没法一篇篇修改好。

深入浅出设计模式【四、建造者模式】

一、建造者模式介绍

建造者模式旨在将一个复杂对象的构建与其表示分离,使得同样的构建过程可以创建不同的表示。

当创建一个对象需要很多步骤,或者需要大量参数(其中许多是可选的或需要复杂初始化)时,直接使用构造方法或 setter 方法会导致代码难以编写、阅读和维护。建造者模式通过提供一个专用的“导演”(Director)和“建造者”(Builder)来一步步指导对象的构造,最终返回一个完整的产品。

二、核心概念与意图

  1. 核心概念

    • 产品 (Product): 最终要构建的复杂对象。
    • 抽象建造者 (Builder): 声明了创建产品各个部件的抽象方法/接口,通常还包括一个返回最终产品的方法(如 getProduct())。
    • 具体建造者 (Concrete Builder): 实现抽象建造者接口,定义具体的构建步骤和逻辑。它持有当前正在构建的产品实例并负责组装它。
    • 导演 (Director): 负责安排建造的步骤和顺序。它接收一个建造者对象,并按照特定的逻辑调用其构建方法。客户端通常与导演交互。
    • 客户端 (Client): 创建导演和建造者对象,并将建造者对象传递给导演以开始构建过程。
  2. 意图

    • 将复杂对象的构建过程分离,使得构建过程可以独立于产品的组成部分。
    • 通过一步步的构建过程,可以精细控制最终产品的内部结构和表示。
    • 避免“伸缩构造函数” (Telescoping Constructor) 和过多的 setter 方法,提高代码的可读性和可维护性。

三、适用场景剖析

建造者模式在以下场景中非常有效:

  1. 创建复杂对象: 当对象的创建过程非常复杂,包含多个步骤或部件,并且这些步骤需要按照特定顺序执行时。
  2. 构造过程需要允许有不同的表示: 同样的构建过程(由导演控制)可以通过不同的具体建造者来创建不同内部表示的产品。
  3. 避免过多构造函数参数: 当对象的构造函数参数过多(特别是许多可选参数)时,使用建造者模式可以避免令人困惑的构造函数重载(伸缩构造函数模式)。
  4. 需要创建不可变对象: 建造者模式非常适合创建不可变对象。可以在建造过程中设置所有属性,并在最终 build() 方法中一次性构造一个完整且不可变的对象。

四、类图解析

以下Mermaid类图清晰地展示了建造者模式的结构和角色间的关系:

builds
Director
-builder: Builder
+construct()
«interface»
Builder
+buildPartA()
+buildPartB()
+buildPartC()
+getResult()
ConcreteBuilder
-product: Product
+buildPartA()
+buildPartB()
+buildPartC()
+getResult()
Product
+setPartA()
+setPartB()
+setPartC()
  • Director: 持有对 Builder 的引用。它的 construct() 方法定义了构建产品的固定步骤和顺序。它只知道要调用哪些构建方法,但不知道具体如何构建。
  • Builder: 抽象接口,声明了构建产品各个部件(buildPartX)和获取最终产品(getResult)的方法。
  • ConcreteBuilder: 实现 Builder 接口。它负责:
    1. 创建和组装产品的各个部件。
    2. 提供一个返回最终构建好的产品的方法。
    3. 可以定义并跟踪当前产品的构建状态。
  • Product: 最终被构建的复杂对象。它包含多个部件。

调用流程: 客户端创建一个 ConcreteBuilder 并将其传递给 DirectorDirector 指导构建过程。最后,客户端从 ConcreteBuilder 中获取构建好的 Product

五、各种实现方式及其优缺点

1. 经典实现(GoF风格)

即上述UML所描述的方式,包含Director和抽象的Builder接口。

  • 优点
    • 分离度高: 将构建过程(Director)、构建实现(ConcreteBuilder)和最终产品(Product)完全分离。
    • 精细控制: 可以对构建过程进行非常精细的控制。
    • 支持不同的表示: 通过替换ConcreteBuilder,可以用相同的构建过程创建不同的产品。
  • 缺点
    • 结构复杂: 需要定义多个类(Director, Builder, ConcreteBuilder),如果产品不复杂,显得过于繁重。
    • 产品必须可分步构建: 要求产品的组成部分必须允许分步设置。

2. 流式接口(Fluent Interface)建造者(现代Java常用)

这种变体将建造者内嵌到产品类中,并省略了Director角色,通过链式方法调用(Method Chaining)来指导构建过程。这是目前最流行、最直观的实现方式。

public class Computer {
    // 必需参数
    private final String cpu;
    private final String ram;
    // 可选参数
    private final int usbCount;
    private final String keyboard;

    // 私有构造函数,只能通过Builder构建
    private Computer(ComputerBuilder builder) {
        this.cpu = builder.cpu;
        this.ram = builder.ram;
        this.usbCount = builder.usbCount;
        this.keyboard = builder.keyboard;
    }

    // 静态内部类:建造者
    public static class ComputerBuilder {
        // 必需参数
        private final String cpu;
        private final String ram;
        // 可选参数 - 有默认值
        private int usbCount = 2;
        private String keyboard = "标准键盘";

        // 构造必需参数
        public ComputerBuilder(String cpu, String ram) {
            this.cpu = cpu;
            this.ram = ram;
        }

        // 设置可选参数的方法,返回this以支持链式调用
        public ComputerBuilder setUsbCount(int usbCount) {
            this.usbCount = usbCount;
            return this;
        }
        public ComputerBuilder setKeyboard(String keyboard) {
            this.keyboard = keyboard;
            return this;
        }

        // 最终的build方法,创建产品实例
        public Computer build() {
            // 可以在此处进行校验
            if (usbCount < 0) {
                throw new IllegalArgumentException("USB数量不能为负");
            }
            return new Computer(this);
        }
    }
}

// 客户端使用
Computer computer = new Computer.ComputerBuilder("Intel i7", "16GB")
                                .setUsbCount(3)
                                .setKeyboard("机械键盘")
                                .build(); // 一次性构建不可变对象
  • 优点
    • 代码可读性极高: 链式调用非常清晰,就像在配置对象一样。
    • 模拟命名可选参数: 解决了Java没有命名可选参数的问题。
    • 可以构建不可变对象: 所有参数在构造函数中一次性设置,对象创建后不可变,是线程安全的。
    • 可进行参数校验: 可以在 build() 方法中进行集中校验,保证对象构建的合法性。
  • 缺点
    • 冗长: 需要为每个产品类编写一个对应的建造者类,代码量会增加。
    • 通常省略Director: 构建逻辑分散在客户端代码中,如果构建顺序非常重要,可能不如经典实现安全。

六、最佳实践

  1. 优先使用流式接口建造者: 对于大多数需要大量可选参数的场景,流式接口建造者是首选,因为它极大地提高了客户端代码的可读性和可维护性。
  2. 将建造者作为静态内部类: 这将建造者与产品紧密关联,并且可以访问产品的私有构造函数,从而强制客户端必须通过建造者来创建产品。
  3. build() 方法中进行校验: 确保在构建对象之前,所有参数组合是有效和一致的。如果无效,抛出 IllegalStateExceptionIllegalArgumentException
  4. 用于创建不可变对象: 这是创建不可变对象的最佳方式之一。将所有属性设置为 final,并通过建造者一次性初始化。
  5. 与工厂模式区分
    • 工厂模式: 关心的是实例化什么对象(what),通常是立即返回创建好的对象。
    • 建造者模式: 关心的是如何实例化对象(how),通过一步步的配置,最后才创建对象。它更适合构建复杂对象。

七、在开发中的演变和应用

建造者模式的思想在现代开发中得到了广泛应用和演变:

  1. Lombok的 @Builder 注解: Java库Lombok通过一个简单的注解自动生成流式接口建造者类的样板代码,极大地减少了编码工作量,使建造者模式的应用变得极其普遍。

    import lombok.Builder;
    import lombok.ToString;
    
    @Builder
    @ToString
    public class User {
        private final String name;
        private final int age;
        private final String email;
    }
    
    // 自动生成的建造者,客户端可直接使用
    User user = User.builder()
                    .name("Alice")
                    .age(30)
                    .email("alice@example.com")
                    .build();
    
  2. Spring Framework

    • BeanDefinitionBuilder: 在Spring底层,用于以编程方式、分步地构建复杂的 BeanDefinition 对象。
    • UriComponentsBuilder: 用于分步构建URI,是流式接口建造者的完美例子。
    UriComponents uriComponents = UriComponentsBuilder
            .fromPath("/api/users/{id}")
            .queryParam("format", "json")
            .buildAndExpand(123);
    
  3. Java 8 Stream API: 虽然形式不同,但 Stream 的构建和收集过程体现了建造者模式的思想。你通过中间操作(filter, map)一步步构建一个处理管道,最后通过终端操作(collect)得到最终结果。

八、真实开发案例(Java语言内部、知名开源框架、工具)

  1. StringBuilder & StringBuffer: 这是建造者模式的经典案例,尽管它们没有完全遵循GoF的定义。它们用于分步构建一个复杂的字符串(产品),最后通过 toString() 方法返回最终产品。

  2. java.nio.IntBuffer / CharBuffer: 这些缓冲区类的创建和配置也使用了建造者模式的思想。

  3. Google Guava: 这个库大量使用了建造者模式。例如:

    • ImmutableList.builder(), ImmutableSet.builder()
    • CacheBuilder: 用于构建一个复杂的缓存实例,是流式接口的典范。
    Cache<String, User> cache = CacheBuilder.newBuilder()
        .maximumSize(1000)
        .expireAfterWrite(10, TimeUnit.MINUTES)
        .concurrencyLevel(4)
        .build();
    
  4. Jackson库: 用于JSON反序列化的 JsonParserFactory 和用于序列化的 JsonGeneratorFactory 都可以看作是建造者,用于配置和构建解析器/生成器实例。

  5. 测试框架(如Mockito): 创建和配置Mock对象的过程也常常采用建造者模式,使得配置非常清晰。

    User mockUser = Mockito.mock(User.class, Mockito.withSettings()
        .name("testUser")
        .defaultAnswer(Mockito.RETURNS_SMART_NULLS));
    

九、总结

方面总结
模式类型创建型设计模式
核心意图将复杂对象的构建与其表示分离,使得同样的构建过程可以创建不同的表示。
关键角色产品 (Product)、建造者 (Builder)、具体建造者 (ConcreteBuilder)、导演 (Director)
主要优点1. 良好的封装性:隐藏产品构建的复杂细节。
2. 良好的灵活性:通过不同的建造者可以实现不同的产品表示。
3. 便于控制构建步骤:导演可以精细控制逐步的构建过程。
4. 解耦:将构建过程与产品本身解耦。
5. 提高可读性(流式):链式调用清晰易懂。
主要缺点1. 增加代码量:需要创建多个新的类。
2. 产品需具备共性:产品之间的差异不能太大,否则需要定义过多的建造者。
3. Director缺失(流式):流式实现中,构建逻辑可能分散在客户端。
适用场景1. 对象有很多可选参数或需要多个初始化步骤。
2. 希望创建不可变对象。
3. 同样的构建过程需要产生不同的结果产品。
4. 对象创建过程复杂。
关系与对比vs. 工厂模式: 工厂关注立即创建产品,建造者关注分步创建复杂产品。
vs. 抽象工厂: 抽象工厂创建产品家族,建造者创建一种复杂产品。
现代应用Lombok @Builder 使其成为Java中处理多参数构造的事实标准GuavaSpring 广泛用于构建复杂组件。

建造者模式,特别是其流式接口变体,是解决“伸缩构造函数”反模式和创建不可变复杂对象的利器。它在现代Java开发中无处不在,极大地提升了代码的可读性、安全性和可维护性,是每一位工程师必须掌握的核心模式。

posted @ 2025-08-29 13:01  NeoLshu  阅读(13)  评论(0)    收藏  举报  来源