设计模式-建造者模式

文章目录

  • 一、概述
    • [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.StringBufferStringBuilder 类似,区别在于 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

相关推荐
多加点辣也没关系2 小时前
设计模式-桥接模式
设计模式·桥接模式
雪度娃娃4 小时前
结构型设计模式——装饰模式
设计模式·装饰器模式
sensen_kiss4 小时前
CPT304 SoftwareEngineeringII 软件工程 2 Pt.4 设计模式(下)
设计模式·软件工程
多加点辣也没关系5 小时前
设计模式-适配器模式
设计模式
Forget the Dream6 小时前
基于适配器模式的 Axios 封装实践
设计模式·typescript·axios·适配器模式
Java面试题总结6 小时前
【设计模式03】使用模版模式+责任链模式优化实战
设计模式·责任链模式
庞轩px6 小时前
Redis工具类重构——从臃肿到优雅的门面模式实践
数据库·redis·设计模式·重构·门面模式·可扩展性·可维护性
Supersist21 小时前
【设计模式03】使用模版模式+责任链模式优化实战
后端·设计模式·代码规范
geovindu1 天前
go: Interpreter Pattern
开发语言·设计模式·golang·解释器模式