设计模式-建造者模式

文章目录

  • 一、概述
    • [1.1 结构与角色](#1.1 结构与角色)
    • [1.2 适用场景](#1.2 适用场景)
  • 二、实现方式
    • [2.1 传统建造者](#2.1 传统建造者)
    • [2.2 链式调用建造者](#2.2 链式调用建造者)
    • [2.3 传统建造者 vs 链式调用建造者](#2.3 传统建造者 vs 链式调用建造者)
  • [三、JDK 源码中的建造者](#三、JDK 源码中的建造者)
    • [3.1 StringBuilder](#3.1 StringBuilder)
    • [3.2 StringBuffer](#3.2 StringBuffer)
    • [3.3 Lombok @Builder](#3.3 Lombok @Builder)
  • 四、总结

一、概述

在日常开发中,我们经常会遇到需要构建复杂对象的场景。比如一个 Computer 对象,它可能有 CPU、内存、硬盘、显卡、显示器、键盘、鼠标等众多属性,而且其中某些属性是必填的,某些是选填的,不同配置组合可以构建出不同规格的电脑。

如果直接使用构造方法来创建这样的对象,就会面临以下问题:

  • 构造参数过多:一个类有十几个属性,构造方法的参数列表会非常长,可读性极差
  • 参数顺序容易混淆:多个同类型参数(如 String cpu, String gpu)很容易传错位置,编译器不会报错
  • 部分属性可选时需要多个重载构造方法:为了应对不同的参数组合,需要提供大量的构造方法重载,形成"伸缩构造方法"反模式

比如下面的代码:

java 复制代码
// 构造参数过多,可读性差,容易传错
Computer computer = new Computer("Intel i7", "16GB", "512GB SSD",
    "RTX 3060", "27inch", "机械键盘", "无线鼠标", "WiFi6", "蓝牙5.0");

这时候,建造者模式(Builder Pattern)就派上用场了。

建造者模式是 Java 中最常用的设计模式之一,这种类型的设计模式属于创建型模式,它将一个复杂对象的构建与它的表示分离,使得同样的构建过程可以创建不同的表示。

简单来说,建造者模式就是将复杂对象的创建过程封装起来,允许客户端通过一步步地指定复杂对象的类型和内容来构建它,而不需要知道内部的构建细节。

核心:将复杂对象的构建过程与表示分离,使得同样的构建过程可以创建不同的表示

1.1 结构与角色

建造者模式包含以下角色:
构建
实现
创建
调用
Director 指挥者
Builder 抽象建造者
Product 产品角色
ConcreteBuilder 具体建造者

  • Product(产品角色):被构建的复杂对象,包含多个组成部分
  • Builder(抽象建造者) :定义创建产品各个部件的抽象接口,通常还包含一个返回最终产品的方法 getResult()
  • ConcreteBuilder(具体建造者):实现 Builder 接口,构建和装配各个部件,定义并明确它所创建的表示,提供一个获取产品对象的方法
  • Director(指挥者):构建一个使用 Builder 接口的对象,它负责控制产品的构建顺序和流程,隔离了客户端与创建过程

1.2 适用场景

  • 需要创建的对象具有较多的属性,且部分属性是可选的
  • 需要创建的对象需要经过多个步骤来构建,且构建过程是稳定的
  • 需要创建不同的表示形式(不同配置组合)的复杂对象
  • 希望避免"伸缩构造方法"反模式

二、实现方式

2.1 传统建造者

传统建造者模式严格按照 GoF 的定义,包含产品、抽象建造者、具体建造者和指挥者四个角色。

以组装电脑为例,电脑有 CPU、内存、硬盘、显卡等部件,不同配置可以组装出不同规格的电脑:

(1)产品角色------电脑

java 复制代码
/**
 * 产品角色:电脑
 */
public class Computer {

    private String cpu;
    private String memory;
    private String hardDisk;
    private String gpu;

    public String getCpu() {
        return cpu;
    }

    public void setCpu(String cpu) {
        this.cpu = cpu;
    }

    public String getMemory() {
        return memory;
    }

    public void setMemory(String memory) {
        this.memory = memory;
    }

    public String getHardDisk() {
        return hardDisk;
    }

    public void setHardDisk(String hardDisk) {
        this.hardDisk = hardDisk;
    }

    public String getGpu() {
        return gpu;
    }

    public void setGpu(String gpu) {
        this.gpu = gpu;
    }

    @Override
    public String toString() {
        return "Computer{" +
                "cpu='" + cpu + '\'' +
                ", memory='" + memory + '\'' +
                ", hardDisk='" + hardDisk + '\'' +
                ", gpu='" + gpu + '\'' +
                '}';
    }
}

(2)抽象建造者

java 复制代码
/**
 * 抽象建造者:电脑建造者
 */
public abstract class ComputerBuilder {

    /**
     * 构建 CPU
     */
    public abstract void buildCpu();

    /**
     * 构建内存
     */
    public abstract void buildMemory();

    /**
     * 构建硬盘
     */
    public abstract void buildHardDisk();

    /**
     * 构建显卡
     */
    public abstract void buildGpu();

    /**
     * 获取构建好的电脑对象
     *
     * @return 电脑对象
     */
    public abstract Computer getResult();
}

(3)具体建造者------高配电脑建造者

java 复制代码
/**
 * 具体建造者:高配电脑建造者
 */
public class HighEndComputerBuilder extends ComputerBuilder {

    private final Computer computer = new Computer();

    @Override
    public void buildCpu() {
        computer.setCpu("Intel i9-13900K");
    }

    @Override
    public void buildMemory() {
        computer.setMemory("32GB DDR5");
    }

    @Override
    public void buildHardDisk() {
        computer.setHardDisk("1TB NVMe SSD");
    }

    @Override
    public void buildGpu() {
        computer.setGpu("RTX 4090");
    }

    @Override
    public Computer getResult() {
        return computer;
    }
}

(4)具体建造者------低配电脑建造者

java 复制代码
/**
 * 具体建造者:低配电脑建造者
 */
public class LowEndComputerBuilder extends ComputerBuilder {

    private final Computer computer = new Computer();

    @Override
    public void buildCpu() {
        computer.setCpu("Intel i3-12100");
    }

    @Override
    public void buildMemory() {
        computer.setMemory("8GB DDR4");
    }

    @Override
    public void buildHardDisk() {
        computer.setHardDisk("256GB SSD");
    }

    @Override
    public void buildGpu() {
        computer.setGpu("集成显卡");
    }

    @Override
    public Computer getResult() {
        return computer;
    }
}

(5)指挥者

java 复制代码
/**
 * 指挥者:电脑组装指挥者
 * 负责控制构建流程和顺序
 */
public class ComputerDirector {

    /**
     * 组装电脑,控制构建顺序
     *
     * @param builder 电脑建造者
     * @return 组装好的电脑
     */
    public Computer construct(ComputerBuilder builder) {
        builder.buildCpu();
        builder.buildMemory();
        builder.buildHardDisk();
        builder.buildGpu();
        return builder.getResult();
    }
}

(6)客户端调用

java 复制代码
public class TraditionalBuilderDemo {
    public static void main(String[] args) {
        // 创建指挥者
        ComputerDirector director = new ComputerDirector();

        // 组装高配电脑
        ComputerBuilder highEndBuilder = new HighEndComputerBuilder();
        Computer highEndComputer = director.construct(highEndBuilder);
        System.out.println(highEndComputer);
        // Computer{cpu='Intel i9-13900K', memory='32GB DDR5', hardDisk='1TB NVMe SSD', gpu='RTX 4090'}

        // 组装低配电脑
        ComputerBuilder lowEndBuilder = new LowEndComputerBuilder();
        Computer lowEndComputer = director.construct(lowEndBuilder);
        System.out.println(lowEndComputer);
        // Computer{cpu='Intel i3-12100', memory='8GB DDR4', hardDisk='256GB SSD', gpu='集成显卡'}
    }
}

说明:传统建造者模式中,指挥者(Director)负责控制构建流程和顺序,客户端只需要告诉指挥者用哪个建造者即可,无需关心具体的构建过程。当需要更换配置时,只需要更换具体建造者。

2.2 链式调用建造者

传统建造者模式虽然结构清晰,但类较多,使用起来比较繁琐。在实际开发中,更常用的是链式调用建造者(也称为简化版建造者),它将建造者作为产品的静态内部类,通过链式调用来设置属性,代码更简洁、可读性更强。

(1)产品 + 内部建造者

java 复制代码
/**
 * 产品角色:手机
 * 内部包含一个静态建造者类
 */
public class Phone {

    private String cpu;
    private String memory;
    private String storage;
    private String screen;
    private String battery;

    /**
     * 私有构造方法,只能通过 Builder 创建
     */
    private Phone(Builder builder) {
        this.cpu = builder.cpu;
        this.memory = builder.memory;
        this.storage = builder.storage;
        this.screen = builder.screen;
        this.battery = builder.battery;
    }

    @Override
    public String toString() {
        return "Phone{" +
                "cpu='" + cpu + '\'' +
                ", memory='" + memory + '\'' +
                ", storage='" + storage + '\'' +
                ", screen='" + screen + '\'' +
                ", battery='" + battery + '\'' +
                '}';
    }

    /**
     * 静态内部建造者
     */
    public static class Builder {

        private String cpu;
        private String memory;
        private String storage;
        private String screen;
        private String battery;

        /**
         * 设置 CPU
         *
         * @param cpu CPU 型号
         * @return Builder 对象本身,支持链式调用
         */
        public Builder cpu(String cpu) {
            this.cpu = cpu;
            return this;
        }

        /**
         * 设置内存
         *
         * @param memory 内存大小
         * @return Builder 对象本身
         */
        public Builder memory(String memory) {
            this.memory = memory;
            return this;
        }

        /**
         * 设置存储
         *
         * @param storage 存储大小
         * @return Builder 对象本身
         */
        public Builder storage(String storage) {
            this.storage = storage;
            return this;
        }

        /**
         * 设置屏幕
         *
         * @param screen 屏幕参数
         * @return Builder 对象本身
         */
        public Builder screen(String screen) {
            this.screen = screen;
            return this;
        }

        /**
         * 设置电池
         *
         * @param battery 电池容量
         * @return Builder 对象本身
         */
        public Builder battery(String battery) {
            this.battery = battery;
            return this;
        }

        /**
         * 构建手机对象
         *
         * @return 手机对象
         */
        public Phone build() {
            // 可以在此处进行参数校验
            if (cpu == null || cpu.isEmpty()) {
                throw new IllegalStateException("CPU 不能为空");
            }
            if (memory == null || memory.isEmpty()) {
                throw new IllegalStateException("内存不能为空");
            }
            return new Phone(this);
        }
    }
}

(2)客户端调用

java 复制代码
public class ChainBuilderDemo {
    public static void main(String[] args) {
        // 链式调用构建手机对象
        Phone phone = new Phone.Builder()
                .cpu("骁龙8 Gen3")
                .memory("16GB")
                .storage("512GB")
                .screen("6.7英寸 AMOLED")
                .battery("5000mAh")
                .build();
        System.out.println(phone);
        // Phone{cpu='骁龙8 Gen3', memory='16GB', storage='512GB', screen='6.7英寸 AMOLED', battery='5000mAh'}

        // 也可以只设置必填属性,选填属性不设置
        Phone simplePhone = new Phone.Builder()
                .cpu("天玑9200")
                .memory("8GB")
                .build();
        System.out.println(simplePhone);
        // Phone{cpu='天玑9200', memory='8GB', storage='null', screen='null', battery='null'}
    }
}

链式调用建造者的优势非常明显:

  • 可读性强:每个方法调用都清楚地表明了设置的属性名称和值
  • 参数顺序无关:不用关心参数的顺序,不会传错
  • 可选属性灵活 :不需要的属性直接不调用对应方法即可,无需传递 null
  • 支持参数校验 :可以在 build() 方法中统一进行参数校验
  • 不可变对象 :产品类的构造方法是私有的,属性可以声明为 final,创建后不可修改

2.3 传统建造者 vs 链式调用建造者

对比维度 传统建造者 链式调用建造者
类的个数 多(产品 + 抽象建造者 + 具体建造者 + 指挥者) 少(产品 + 内部 Builder)
指挥者 有,控制构建流程 无,客户端自行链式调用
可读性 一般 强(链式调用语义清晰)
扩展性 好(新增具体建造者即可) 一般(新增产品需新建 Builder)
适用场景 需要多种表示形式的复杂产品 属性较多的单个产品
典型应用 框架设计 日常业务开发

选型建议:

  • 如果需要构建同一产品的多种不同表示(如不同配置的电脑),使用传统建造者
  • 如果只是需要创建属性较多的对象,使用链式调用建造者即可,代码更简洁

三、JDK 源码中的建造者

建造者模式在 JDK 及主流框架中有着广泛的应用,下面来看几个经典的例子。

3.1 StringBuilder

java.lang.StringBuilder 就是一个非常典型的建造者模式的应用,它通过 append() 方法逐步构建字符串,最终通过 toString() 方法获取构建结果。

java 复制代码
// StringBuilder 的建造者模式应用
public final class StringBuilder extends AbstractStringBuilder
        implements java.io.Serializable, CharSequence {

    /**
     * 构造方法
     */
    public StringBuilder() {
        super(16);
    }

    /**
     * 追加字符串 ------ 相当于建造者的 buildPart() 方法
     */
    @Override
    public StringBuilder append(String str) {
        super.append(str);
        return this; // 返回自身,支持链式调用
    }

    /**
     * 追加整数
     */
    @Override
    public StringBuilder append(int i) {
        super.append(i);
        return this;
    }

    /**
     * 获取最终结果 ------ 相当于建造者的 getResult() 方法
     */
    @Override
    public String toString() {
        // Create a copy, don't share the array
        return new String(value, 0, count);
    }
}

使用方式:

java 复制代码
StringBuilder sb = new StringBuilder();
sb.append("Hello").append(" ").append("World").append(2024);
String result = sb.toString(); // "Hello World2024"

说明 :StringBuilder 中的 append() 方法返回 this,这就是链式调用的关键。append() 相当于建造者的 buildPart() 方法,toString() 相当于 getResult() 方法。

3.2 StringBuffer

java.lang.StringBuffer 与 StringBuilder 类似,区别在于 StringBuffer 的方法是线程安全的(加了 synchronized 关键字)。

java 复制代码
public final class StringBuffer extends AbstractStringBuilder
        implements java.io.Serializable, CharSequence {

    @Override
    public synchronized StringBuffer append(String str) {
        super.append(str);
        return this;
    }

    @Override
    public synchronized String toString() {
        return new String(value, 0, count);
    }
}

3.3 Lombok @Builder

在日常开发中,我们经常使用 Lombok 的 @Builder 注解来快速生成建造者模式代码,其原理就是在编译期自动生成内部 Builder 类。

使用 Lombok @Builder 注解:

java 复制代码
import lombok.Builder;
import lombok.ToString;

@Builder
@ToString
public class User {

    private String name;
    private Integer age;
    private String email;
    private String phone;
}

Lombok 编译后自动生成的等价代码(简化版):

java 复制代码
public class User {

    private String name;
    private Integer age;
    private String email;
    private String phone;

    User(String name, Integer age, String email, String phone) {
        this.name = name;
        this.age = age;
        this.email = email;
        this.phone = phone;
    }

    public static UserBuilder builder() {
        return new UserBuilder();
    }

    public static class UserBuilder {

        private String name;
        private Integer age;
        private String email;
        private String phone;

        UserBuilder() {}

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

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

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

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

        public User build() {
            return new User(name, age, email, phone);
        }
    }
}

客户端使用:

java 复制代码
User user = User.builder()
        .name("张三")
        .age(25)
        .email("zhangsan@example.com")
        .build();
// User(name=张三, age=25, email=zhangsan@example.com, phone=null)

可以看到,Lombok 帮我们省去了大量样板代码,让建造者模式的使用变得非常简单。


四、总结

建造者模式的核心思想是将复杂对象的构建过程与表示分离,使得同样的构建过程可以创建不同的表示,客户端不需要知道对象的具体构建细节。

优点:

  • 封装性好:客户端不需要知道产品内部的组成细节,将产品本身与产品的创建过程解耦
  • 可读性强:链式调用方式使得代码语义清晰,容易理解
  • 灵活可控:可以精细地控制对象的创建过程,支持参数校验和默认值
  • 符合开闭原则(传统建造者):新增具体建造者无需修改已有代码

缺点:

  • 类的个数增加:需要额外创建 Builder 类,增加了系统的类的数量
  • 产品内部变化时建造者需同步修改:如果产品的属性发生变化,建造者也需要相应修改
  • 传统建造者模式结构较复杂,对于简单对象有些"杀鸡用牛刀"

适用场景:

  • 对象有较多的属性,且部分属性是可选的
  • 需要创建的对象需要经过多个步骤来构建,且构建过程需要控制
  • 需要创建不同表示形式的复杂对象
  • 希望避免"伸缩构造方法"反模式
  • 希望创建不可变对象

创建型模式小结:至此,5 种创建型模式已经全部介绍完毕------单例模式保证唯一实例、工厂方法模式将创建逻辑委托给子类、抽象工厂模式创建产品族、建造者模式分步构建复杂对象、原型模式通过克隆创建对象。它们各有侧重,根据实际场景灵活选择即可。


参考博客:

建造者模式 | 菜鸟教程:https://www.runoob.com/design-pattern/builder-pattern.html

相关推荐
染指111014 小时前
140.Agent-多Agent框架-Agent执行Skills流程
数据库·人工智能·设计模式·langchain·agents
sarasuki20 小时前
Agent 是怎么做错误处理的呢?
人工智能·设计模式·agent
A1Book20 小时前
从 0 设计历史专题资料汇编的 AI Agent 编排架构——六轮真实任务复盘
设计模式
geovindu2 天前
rust: Simple Factory Pattern(续)
后端·设计模式·rust·简单工厂模式·创建型模式
喜欢coding的谢同学2 天前
Go语言常用设计模式基础和实践案例介绍
开发语言·设计模式·golang
sarasuki3 天前
Agent 的记忆有几种?你的名字是?
人工智能·设计模式·agent
geovindu3 天前
rust: Flyweight Pattern
开发语言·后端·设计模式·rust·享元模式·结构型模式
沫璃染墨4 天前
《Linux工程实践篇(一):认识设计模式——从日志系统看策略模式》
linux·c++·安全·设计模式·策略模式
十六宿舍4 天前
【软件设计模式-第一课:基础知识了解】
设计模式·架构·面向对象·软件架构
geovindu4 天前
rust: Facade Pattern
开发语言·后端·设计模式·rust·外观模式