基于本文回答
0
评论

设计模式--建造者模式 (Builder)

知识点图片

建造者模式将复杂对象的构建与其表示分离,用于分步创建一个复杂对象。它解决了构造函数参数过多的问题,并能使用同一构建过程创建不同表示的对象。

我们来详细地解析一下设计模式中的建造者模式(Builder Pattern)

我会从以下几个方面来为你讲解:

  1. 什么是建造者模式?(定义和比喻)
  2. 为什么要使用建造者模式?(解决的问题)
  3. 建造者模式的结构和角色
  4. 代码实现(两种常见形式)
    • 经典形式(带指导者 Director)
    • 链式调用形式(更常用)
  5. 优缺点分析
  6. 适用场景
  7. 与工厂模式的区别

1. 什么是建造者模式?

核心思想: 将一个复杂对象构建过程与其表示分离,使得同样的构建过程可以创建出不同的表示。

简单来说,建造者模式关心的是如何一步一步地创建一个复杂对象,它允许你使用相同的构建代码生成不同类型和表示的对象。

一个生动的比喻:

想象一下去快餐店点一个自定义套餐(比如赛百味或组装电脑)。

  • 产品(Product):你最终得到的那个汉堡或电脑。它很复杂,由多个部分组成(面包、肉、蔬菜、酱料等)。
  • 抽象建造者(Builder):一个标准的“配餐单”,上面列出了所有可添加的步骤,比如 addBread(), addMeat(), addVegetable()。但它不规定具体加哪种面包或肉。
  • 具体建造者(Concrete Builder):厨师 A 和厨师 B。他们都拿着同样的配餐单。
    • 厨师 A(豪华牛肉套餐建造者)按照配餐单,加入了全麦面包、双层牛肉、生菜和番茄酱。
    • 厨师 B(健康鸡肉套餐建造者)按照配餐单,加入了白面包、烤鸡肉、所有蔬菜和零卡酱。
  • 指导者(Director):你自己(或者一个固定的套餐流程)。你告诉厨师:“先加面包,再加肉,最后加蔬菜和酱料”。这个顺序和流程是固定的。你不需要关心厨师具体是怎么做牛肉的,你只负责指挥构建的步骤

通过这个过程,你(指导者)使用同样的指令(构建过程),让不同的厨师(具体建造者)做出了完全不同风味的汉堡(不同的表示)。

2. 为什么要使用建造者模式?

主要为了解决以下两个问题:

  1. 构造函数参数过多(Telescoping Constructor Problem)
    当一个类有很多属性,其中一些是必需的,一些是可选的,你可能会创建很多个构造函数:

    java
    // 糟糕的设计:伸缩构造函数
    public class Computer {
        // 必选
        private String cpu;
        private int ram;
        // 可选
        private int ssd;
        private String gpu;
        private String keyboard;
    
        public Computer(String cpu, int ram) { ... }
        public Computer(String cpu, int ram, int ssd) { ... }
        public Computer(String cpu, int ram, int ssd, String gpu) { ... }
        // ... 还有很多组合
    }
    // 调用时非常痛苦
    Computer pc = new Computer("Intel i7", 16, 512, "RTX 3080", null);

    这样的代码可读性差,容易传错参数,且无法清晰地表达每个参数的含义。

  2. 对象状态不一致
    另一种方法是使用无参构造函数和 setter 方法:

    java
    Computer pc = new Computer();
    pc.setCpu("Intel i7");
    pc.setRam(16);
    // ...
    // 在调用完所有 setter 之前,pc 对象可能处于一个不完整或无效的状态

    这种方式下,对象在完全构建好之前,可能被其他代码使用,导致不可预期的错误。

建造者模式通过将构建过程封装起来,完美地解决了这两个问题。

3. 建造者模式的结构和角色

  • Product(产品):要创建的复杂对象。它包含了多个组成部分。
  • Builder(抽象建造者):一个接口,定义了创建产品各个部分的方法(如 buildPartA(), buildPartB())以及一个返回最终产品的方法 getResult()
  • ConcreteBuilder(具体建造者):实现了 Builder 接口。它知道如何构建和装配产品的各个部分,并负责维护其正在创建的产品实例。
  • Director(指导者):负责管理构建过程。它持有一个 Builder 对象的引用,并调用 Builder 的方法来构建产品。Director 定义了构建的顺序和算法,将客户端与具体的构建过程解耦。(注意:在现代的链式调用实现中,这个角色常常被省略)

4. 代码实现

形式一:经典形式(带 Director)

这种形式最符合 GoF 的原始定义,强调“构建过程”的稳定。

场景: 组装电脑,分为游戏电脑和办公电脑。

1. 产品 (Product)

java
// Computer.java
public class Computer {
    private String cpu;
    private String ram;
    private String ssd;
    private String gpu;

    // Setter methods
    public void setCpu(String cpu) { this.cpu = cpu; }
    public void setRam(String ram) { this.ram = ram; }
    public void setSsd(String ssd) { this.ssd = ssd; }
    public void setGpu(String gpu) { this.gpu = gpu; }

    @Override
    public String toString() {
        return "Computer [cpu=" + cpu + ", ram=" + ram + ", ssd=" + ssd + ", gpu=" + gpu + "]";
    }
}

2. 抽象建造者 (Builder)

java
// ComputerBuilder.java
public interface ComputerBuilder {
    void buildCpu();
    void buildRam();
    void buildSsd();
    void buildGpu();
    Computer getComputer();
}

3. 具体建造者 (ConcreteBuilder)

java
// GamingComputerBuilder.java
public class GamingComputerBuilder implements ComputerBuilder {
    private Computer computer = new Computer();

    @Override
    public void buildCpu() {
        computer.setCpu("Intel i9");
    }
    @Override
    public void buildRam() {
        computer.setRam("32GB DDR5");
    }
    @Override
    public void buildSsd() {
        computer.setSsd("1TB NVMe SSD");
    }
    @Override
    public void buildGpu() {
        computer.setGpu("NVIDIA RTX 4090");
    }
    @Override
    public Computer getComputer() {
        return computer;
    }
}

// OfficeComputerBuilder.java
public class OfficeComputerBuilder implements ComputerBuilder {
    private Computer computer = new Computer();
    
    @Override
    public void buildCpu() {
        computer.setCpu("Intel i5");
    }
    @Override
    public void buildRam() {
        computer.setRam("16GB DDR4");
    }
    @Override
    public void buildSsd() {
        computer.setSsd("512GB SATA SSD");
    }
    @Override
    public void buildGpu() {
        // 办公电脑通常使用集成显卡
        computer.setGpu("Integrated Graphics");
    }
    @Override
    public Computer getComputer() {
        return computer;
    }
}

4. 指导者 (Director)

java
// ComputerDirector.java
public class ComputerDirector {
    // 指挥构建过程
    public Computer construct(ComputerBuilder builder) {
        builder.buildCpu();
        builder.buildRam();
        builder.buildSsd();
        builder.buildGpu();
        return builder.getComputer();
    }
}

5. 客户端调用

java
public class Client {
    public static void main(String[] args) {
        ComputerDirector director = new ComputerDirector();

        // 构建一台游戏电脑
        ComputerBuilder gamingBuilder = new GamingComputerBuilder();
        Computer gamingComputer = director.construct(gamingBuilder);
        System.out.println("Gaming Computer: " + gamingComputer);

        // 构建一台办公电脑
        ComputerBuilder officeBuilder = new OfficeComputerBuilder();
        Computer officeComputer = director.construct(officeBuilder);
        System.out.println("Office Computer: " + officeComputer);
    }
}

形式二:链式调用(Fluent Interface)

这是目前更流行、更实用的形式,常见于各种框架和库中(如 StringBuilder, OkHttp 的 Request.Builder, Lombok 的 @Builder 注解)。它通常省略了 Director 角色,将构建的灵活性直接交给客户端。

1. 产品 (Product) - 稍作修改

java
// Computer.java
public class Computer {
    private final String cpu; // 使用 final 确保不可变
    private final String ram;
    private final String ssd;
    private final String gpu;

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

    @Override
    public String toString() {
        return "Computer [cpu=" + cpu + ", ram=" + ram + ", ssd=" + ssd + ", gpu=" + gpu + "]";
    }
    
    // 静态内部类 Builder
    public static class Builder {
        // 必选参数
        private final String cpu;
        private final String ram;
        
        // 可选参数
        private String ssd = "256GB"; // 可以有默认值
        private String gpu = "Integrated";

        public Builder(String cpu, String ram) {
            this.cpu = cpu;
            this.ram = ram;
        }

        public Builder ssd(String ssd) {
            this.ssd = ssd;
            return this; // 返回 this 以支持链式调用
        }

        public Builder gpu(String gpu) {
            this.gpu = gpu;
            return this; // 返回 this
        }

        // 最终的 build 方法
        public Computer build() {
            // 可以在这里进行参数校验
            return new Computer(this);
        }
    }
}

2. 客户端调用

java
public class Client {
    public static void main(String[] args) {
        // 使用链式调用构建对象
        Computer gamingComputer = new Computer.Builder("Intel i9", "32GB")
                .ssd("2TB NVMe SSD")
                .gpu("NVIDIA RTX 4090")
                .build();

        Computer officeComputer = new Computer.Builder("Intel i5", "16GB")
                .ssd("512GB SATA SSD")
                // 不设置 gpu,使用默认值
                .build();

        System.out.println("Gaming Computer: " + gamingComputer);
        System.out.println("Office Computer: " + officeComputer);
    }
}

这种形式可读性极强,并且通过将构造函数私有化和在 build() 方法中返回最终对象,保证了对象的不可变性状态一致性

5. 优缺点分析

优点:

  1. 封装性好:将构建细节封装在 ConcreteBuilder 中,客户端无需知道内部的构建逻辑。
  2. 易于扩展:添加一个新的产品表示,只需要增加一个新的 ConcreteBuilder 类即可,符合“开闭原则”。
  3. 更好的控制
    • 经典模式中,Director 控制了构建的顺序和过程,确保了构建的稳定性。
    • 链式模式中,客户端可以自由地、清晰地配置对象的属性,可读性高。
  4. 将构建和表示分离:使得相同的构建过程可以创建出不同的产品,复用性好。

缺点:

  1. 增加了代码量:引入了 Builder、Director 等角色,使得类的数量增多,系统结构更复杂。
  2. 不适用于简单对象:如果一个对象不够复杂,属性不多,使用建造者模式会显得“杀鸡用牛刀”,增加不必要的复杂性。

6. 适用场景

  1. 当一个对象的创建过程很复杂,由多个部分组成,并且这些部分的创建顺序会影响最终结果时。
  2. 当需要创建的对象具有很多可选参数或配置时(完美替代伸缩构造函数)。
  3. 当希望隔离复杂对象的创建和使用,并让客户端能够自定义对象的具体构成时。
  4. 当需要创建同一类的不同变体(表示)时,例如,创建一套“豪华版”和“标准版”的配置。

7. 与工厂模式的区别

这是面试中经常被问到的问题,两者都用于创建对象,但侧重点完全不同。

特性 建造者模式 (Builder) 工厂模式 (Factory)
关注点 过程:关注如何一步一步地创建出一个复杂对象。它是一个“施工队”。 结果:关注如何直接创建出一个完整的对象实例。它是一个“生产线”。
对象复杂度 主要用于复杂对象的创建。 可用于简单对象,也可用于复杂对象。
创建方式 客户端(或 Director)参与了对象的构建过程,可以分步进行。 客户端直接向工厂请求一个对象,不关心创建细节。
产品差异性 产出的产品通常是同一类型但配置不同的对象。 抽象工厂模式产出的是一族相关但不同类型的对象(如WindowsButtonMacButton)。
抽象级别 更高的抽象层次,将构建算法和具体实现分离。 抽象层次相对较低,主要隐藏了 new 的过程。

简单总结: 如果你想知道一个对象是如何被组装起来的,并且想控制这个组装过程,用建造者模式。如果你只关心得到一个什么类型的对象,不关心它是怎么来的,用工厂模式

右滑查看面试常问