每天认识一个设计模式-建造者模式:复杂对象的“装配式革命“

发布于:2025-03-28 ⋅ 阅读:(24) ⋅ 点赞:(0)

一、前言

在软件开发的广袤领域中,随着项目规模日益庞大、业务逻辑愈发复杂,对象的创建过程也变得千头万绪。

早期简单的对象创建方式,在面对复杂对象时,逐渐显露出代码臃肿、耦合度高、可维护性差等弊端,设计模式的演进迫在眉睫。

建造者模式正是在这样的背景下应运而生,它致力于解决如何将复杂对象的构建过程与其表示分离,使得同样的构建过程能够创建不同的表示这一核心问题,宛如一场 “装配式革命”,为复杂对象的创建带来全新的思路与高效的解决方案 。

二、建造者模式介绍 

建造者模式(Builder Pattern)是一种创建型设计模式,它将一个复杂对象的构建与它的表示分离,使得同样的构建过程可以创建不同的表示。

这一模式就像是建筑工人按照既定的流程,使用不同的建筑材料来打造风格各异的房屋。在软件开发中,当我们需要创建的对象包含多个复杂的组成部分,并且创建过程涉及多个步骤时,建造者模式便大有用武之地。

它通过将对象创建过程封装在具体的建造者类中,使代码的结构更加清晰,也提高了可维护性和可扩展性。​

建造者模式主要包含以下几个角色:

  1. 产品(Product):要创建的复杂对象。
  2. 抽象建造者(Builder):为创建一个 Product 对象的各个部件指定抽象接口。
  3. 具体建造者(ConcreteBuilder):实现 Builder 接口,构造和装配各个部件。
  4. 指挥者(Director):构建一个使用 Builder 接口的对象,它主要负责控制建造的过程和步骤。

为了更深入理解建造者模式的内部运作机制,我们借助 UML 图来分析其中的关键角色。

  • Product 类表示最终的产品(如汽车),包含一些部件属性和展示方法。
  • Builder 接口定义了创建产品部件的方法以及获取最终产品的方法。
  • ConcreteBuilder1 和 ConcreteBuilder2 是具体的建造者实现类,负责具体的部件构建。
  • Director 类负责指挥建造过程,使用 Builder 来构造产品。

通过这些角色的协同工作,建造者模式有条不紊地完成复杂对象的创建,让开发者能够更高效地处理复杂对象的构建逻辑。 

 三、实际框架中的建造者模式分析

在我们进行建造者模式实践之前,其实开发中很多熟悉场景都能见到建造者模式在源码中的应用:

3.1.Spring 框架

如果你研究过 Spring 框架,那么肯定知道BeanDefinition类与BeanFactory类协同工作,这里充分体现了建造者模式。BeanDefinition就像是具体建造者,它存储了 Bean 的各种元信息,例如类名、构造函数参数、属性值等,这些信息用于描述如何构建一个 Bean。

GenericBeanDefinition beanDefinition = new GenericBeanDefinition();
beanDefinition.setBeanClass(MyService.class);
MutablePropertyValues propertyValues = new MutablePropertyValues();
propertyValues.add("dependency", new Dependency());
beanDefinition.setPropertyValues(propertyValues);

上述代码中,GenericBeanDefinition实例化后,设置了要创建的 Bean 的类(MyService.class),并添加了属性值(这里添加了一个依赖对象dependency)。而BeanFactory则扮演指挥者的角色,它根据BeanDefinition中的信息来创建和管理 Bean。

在 Spring 的核心源码中,DefaultListableBeanFactory类是BeanFactory的一个重要实现类,其createBean方法如下:

@Override
public Object createBean(String beanName, RootBeanDefinition mbd, @Nullable Object[] args) throws BeanCreationException {
    // 解析BeanDefinition中的信息,创建Bean实例
    // 包括处理构造函数、属性注入等操作
    // 具体逻辑较为复杂,此处简化示意
    Object beanInstance = instantiateBean(beanName, mbd);
    populateBean(beanName, mbd, beanInstance);
    return beanInstance;
}

createBean方法根据BeanDefinition(mbd)中的信息,先实例化 Bean(instantiateBean方法),再进行属性填充(populateBean方法),最终返回构建好的 Bean 实例,完整呈现了建造者模式中指挥者安排构建步骤,具体建造者提供构建信息的过程。

3.2.MyBatis 框架

除了Spring框架外,在ORM框架MyBatis源码中也有其具体的应用。SqlSessionFactoryBuilder类作为具体建造者,负责构建SqlSessionFactory这个复杂对象。其核心源码中的build方法如下:

public SqlSessionFactory build(Reader reader, String environment, Properties properties) {
    try {
        XMLConfigBuilder parser = new XMLConfigBuilder(reader, environment, properties);
        return build(parser.parse());
    } catch (Exception e) {
        throw ExceptionFactory.wrapException("Error building SqlSession.", e);
    } finally {
        ErrorContext.instance().reset();
        try {
            reader.close();
        } catch (IOException e) {
            // 忽略异常
        }
    }
}

在这个build方法中,首先通过XMLConfigBuilder解析配置文件(reader),获取配置信息。XMLConfigBuilder解析配置文件后,会构建出一个Configuration对象,这个对象包含了数据源、事务管理、映射器等各种配置信息,类似于建造者模式中构建复杂对象的各个部分。

然后SqlSessionFactoryBuilder再基于这个Configuration对象构建SqlSessionFactory,最终返回构建好的SqlSessionFactory对象,清晰地展示了建造者模式在构建复杂对象过程中的应用。

3.3Lombok 框架

Lombok 的@Builder注解为 Java 类的构建提供了一种便捷的建造者模式实现方式。例如,有一个User类:

@Builder
public class User {
    private String name;
    private int age;
    private String email;
}

当使用@Builder注解后,Lombok 在编译时会自动生成一个建造者类,其大致结构如下(简化示意):

public class UserBuilder {
    private String name;
    private int age;
    private String email;

    public UserBuilder name(String name) {
        this.name = name;
        return this;
    }

    public UserBuilder age(int age) {
        this.age = age;
        return this;
    }

    public UserBuilder email(String email) {
        this.email = email;
        return this;
    }

    public User build() {
        User user = new User();
        user.name = this.name;
        user.age = this.age;
        user.email = this.email;
        return user;
    }
}

这里UserBuilder类就像是一个具体建造者,它通过一系列的setter方法(如name、age、email方法)设置User对象的属性,最后通过build方法构建出完整的User对象。

我们可以通过链式调用这些方法来构建User对象,如User user = User.builder().name("John").age(30).email("john@example.com").build();,极大地简化了复杂对象的构建过程,这正是建造者模式的典型应用场景。

四、建造者模式的自定义设计

介绍完这么多,虽然源码都很高大尚,但是我们也可以根据自己平时在开发过程中的需求去应用建造者模式。

4.1.静态内部类构建器实现

在实际开发中,当我们需要创建一个复杂的对象,并且希望创建过程具有一定的灵活性和可读性时,静态内部类构建器是一种常用的方式。例如,考虑一个电商系统中的Order类,一个订单可能包含订单编号、客户信息、商品列表、配送地址等多个属性。

public class Order {
    private String orderId;
    private Customer customer;
    private List<Product> products;
    private String shippingAddress;

    // 私有构造函数,防止外部直接实例化
    private Order(Builder builder) {
        this.orderId = builder.orderId;
        this.customer = builder.customer;
        this.products = builder.products;
        this.shippingAddress = builder.shippingAddress;
    }

    // 静态内部类作为构建器
    public static class Builder {
        private String orderId;
        private Customer customer;
        private List<Product> products = new ArrayList<>();
        private String shippingAddress;

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

        public Builder setCustomer(Customer customer) {
            this.customer = customer;
            return this;
        }

        public Builder addProduct(Product product) {
            products.add(product);
            return this;
        }

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

        public Order build() {
            // 可以在这里进行一些必要的校验
            if (orderId == null || customer == null || products.isEmpty() || shippingAddress == null) {
                throw new IllegalArgumentException("Order information is incomplete");
            }
            return new Order(this);
        }
    }
}

在上述代码中,Order类的构造函数被私有化,外部只能通过Builder静态内部类来创建Order对象。Builder类提供了一系列的setter方法,用于设置Order对象的各个属性。最后通过build方法进行一些必要的校验(如订单信息是否完整),然后返回构建好的Order对象。在实际使用时:

Customer customer = new Customer("John Doe", "john@example.com");
Product product1 = new Product("Book", 29.99);
Product product2 = new Product("Pen", 2.99);
Order order = new Order.Builder()
      .setOrderId("12345")
      .setCustomer(customer)
      .addProduct(product1)
      .addProduct(product2)
      .setShippingAddress("123 Main St")
      .build();

这种方式使得Order对象的创建过程清晰明了,并且方便后续对创建逻辑进行扩展和维护。

4.2.流式接口与链式调用

流式接口与链式调用是建造者模式的一种常见表现形式,它可以让代码更加简洁和易读。以游戏开发中创建一个复杂游戏角色为例,假设我们有一个CharacterBuilder类用于构建游戏角色,角色具有名称、种族、职业、装备以及技能等属性。

public class Character {
    private String name;
    private String race;
    private String profession;
    private List<String> equipments = new ArrayList<>();
    private List<String> skills = new ArrayList<>();

    private Character(CharacterBuilder builder) {
        this.name = builder.name;
        this.race = builder.race;
        this.profession = builder.profession;
        this.equipments = builder.equipments;
        this.skills = builder.skills;
    }

    @Override
    public String toString() {
        return "Character{" +
                "name='" + name + '\'' +
                ", race='" + race + '\'' +
                ", profession='" + profession + '\'' +
                ", equipments=" + equipments +
                ", skills=" + skills +
                '}';
    }
}

public class CharacterBuilder {
    private String name;
    private String race;
    private String profession;
    private List<String> equipments = new ArrayList<>();
    private List<String> skills = new ArrayList<>();

    public CharacterBuilder setName(String name) {
        this.name = name;
        return this;
    }

    public CharacterBuilder setRace(String race) {
        this.race = race;
        return this;
    }

    public CharacterBuilder setProfession(String profession) {
        this.profession = profession;
        return this;
    }

    public CharacterBuilder addEquipment(String equipment) {
        equipments.add(equipment);
        return this;
    }

    public CharacterBuilder addSkill(String skill) {
        skills.add(skill);
        return this;
    }

    public Character build() {
        if (name == null || race == null || profession == null) {
            throw new IllegalArgumentException("Character basic information is incomplete");
        }
        return new Character(this);
    }
}

在CharacterBuilder类中,每个设置属性的方法都返回this,从而支持链式调用。在实际的游戏角色创建场景中:

Character warrior = new CharacterBuilder()
      .setName("Conan")
      .setRace("Human")
      .setProfession("Warrior")
      .addEquipment("Sword")
      .addEquipment("Shield")
      .addSkill("Slash")
      .addSkill("Block")
      .build();
System.out.println(warrior);
// 输出: Character{name='Conan', race='Human', profession='Warrior', equipments=[Sword, Shield], skills=[Slash, Block]}

通过这种流式接口与链式调用的方式,开发者能够像搭建积木一样,按照游戏设计需求逐步构建复杂的游戏角色,极大地提升了代码的可读性与可维护性。无论是新增角色属性,还是修改角色构建逻辑,都能在这个清晰的结构中轻松实现。

4.3.Spring 的 build 设计模式运用

在 Spring 框架中,我们可以借鉴建造者模式的思想进行自定义设计。例如,在构建一个复杂的RestTemplate配置时。RestTemplate用于在 Spring 应用中进行 HTTP 请求操作,它可能需要配置连接池、超时时间、拦截器等多个参数。

import org.springframework.http.client.ClientHttpRequestFactory;
import org.springframework.http.client.SimpleClientHttpRequestFactory;
import org.springframework.web.client.RestTemplate;

public class RestTemplateBuilder {
    private int connectTimeout = 5000;
    private int readTimeout = 5000;
    private ClientHttpRequestFactory requestFactory;

    public RestTemplateBuilder setConnectTimeout(int connectTimeout) {
        this.connectTimeout = connectTimeout;
        return this;
    }

    public RestTemplateBuilder setReadTimeout(int readTimeout) {
        this.readTimeout = readTimeout;
        return this;
    }

    public RestTemplateBuilder setRequestFactory(ClientHttpRequestFactory requestFactory) {
        this.requestFactory = requestFactory;
        return this;
    }

    public RestTemplate build() {
        if (requestFactory == null) {
            SimpleClientHttpRequestFactory factory = new SimpleClientHttpRequestFactory();
            factory.setConnectTimeout(connectTimeout);
            factory.setReadTimeout(readTimeout);
            requestFactory = factory;
        }
        return new RestTemplate(requestFactory);
    }
}

这里RestTemplateBuilder类类似于一个建造者,通过一系列的setter方法设置RestTemplate的相关参数。在build方法中,如果没有自定义的ClientHttpRequestFactory,则创建一个默认的SimpleClientHttpRequestFactory并设置连接超时和读取超时时间,最后构建出RestTemplate对象。在 Spring 的配置类中使用时:

import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.client.RestTemplate;

@Configuration
public class AppConfig {
    @Bean
    public RestTemplate restTemplate() {
        return new RestTemplateBuilder()
              .setConnectTimeout(3000)
              .setReadTimeout(3000)
              .build();
    }
}

通过这种方式,我们可以根据不同的业务需求灵活配置RestTemplate,同时保持代码的清晰和可维护性,这正是借鉴了建造者模式的设计思想。

五、建造者模式的适用场景及注意事项

推荐使用场景
✅ 需要创建包含多个属性的不可变对象
✅ 参数之间存在依赖关系(如:先设置协议才能设置端口)
✅ 需要支持不同配置变体(如:开发/生产环境配置)

反模式场景
❌ 简单对象创建(少于4个参数)
❌ 构建过程不需要校验逻辑
❌ 对象属性之间存在强耦合关系

分类

详情

示例

适用场景

复杂对象创建:对象由众多复杂部分构成,构建过程繁琐

电商系统中订单对象,涵盖订单基本信息、明细、配送及支付信息

多步骤构建:对象构建需按特定顺序执行多个步骤

构建操作系统安装包,依次完成收集配置、下载文件、整合驱动、生成引导程序等步骤

创建不同表示的对象:期望用相同构建过程生成不同表现形式对象

图形绘制系统中,同一绘制构建过程生成屏幕显示和打印输出的不同图形

注意事项

性能问题:可能产生额外对象创建和方法调用开销,简单对象使用可能致代码臃肿

简单数据类,如仅含姓名、年龄的 User 类,用建造者模式会增加不必要开销

代码复杂度增加:涉及多个角色,小型项目过度使用会使代码难理解和维护

小型脚本项目,若使用建造者模式创建简单工具对象,会增加项目复杂度

建造者逻辑一致性:各具体建造者构建逻辑应保持一致,否则代码难维护

不同游戏角色建造者,若对角色属性构建逻辑差异大,会使代码混乱

构建步骤顺序的重要性:构建步骤顺序影响产品正确性

汽车建造,发动机和车身安装顺序不同,会影响汽车整体结构和功能

六、总结 

建造者模式作为一种强大的创建型设计模式,在软件开发领域发挥着独特的作用。其核心在于将复杂对象的构建过程与表示分离,赋予开发者清晰的对象构建逻辑。​

建造者模式的优势​

  • 提升代码可读性与可维护性:面对复杂对象创建,将构建逻辑封装于具体建造者类。以电商系统订单对象创建为例:​
  • 订单基本信息构建逻辑清晰,便于理解与维护。​
  • 订单明细的构建过程独立且易读,利于后续调整。​
  • 配送及支付信息的构建同样条理分明,降低维护难度。​
  • 增强代码灵活性:同一构建过程可创建不同表示的对象。比如在图形绘制系统中:​
  • 能利用相同构建过程生成用于屏幕显示的图形对象。​
  • 也可创建用于打印输出的图形对象,满足多样化业务需求。​

与其他设计模式的对比​

与工厂模式对比:​

  • 工厂模式:侧重于整体创建对象,适用于创建简单对象场景。例如,可快速创建仅包含基本属性的简单用户对象。​
  • 建造者模式:专注于复杂对象的分步构建,在构建包含多种复杂属性的电商订单时优势显著。电商订单不仅有基本信息,还涉及订单明细、配送及支付信息等复杂构建过程,建造者模式能更好地处理。​

与抽象工厂模式对比:​

  • 抽象工厂模式:提供一个创建一系列相关或相互依赖对象的接口。例如在游戏开发中,用于创建一系列如角色、武器、装备等相互关联的游戏元素。​
  • 建造者模式:强调构建过程的分步骤与对象表示的分离。例如在构建复杂的房屋对象时,通过分步骤构建地基、墙体、屋顶等,最终呈现出完整的房屋表示。​

希望通过对建造者模式的深入探讨,大伙能在实际开发中敏锐识别适用场景,灵活运用这一模式。当面临复杂对象创建难题时,不妨尝试借助建造者模式,优化代码结构,提升软件质量,让开发工作更加高效且富有成效,在不同项目场景中充分发挥其优势,打造更健壮、可扩展的软件系统 。

 往期相关文章

每天认识一个设计模式 - 单例模式:独一无二的对象管家

每天认识一个设计模式 - 代理模式:程序世界的 “代言人”

每天认识一个设计模式 - 工厂模式:代码世界的“流水线“是怎样炼成的?