建造者模式:链式调用、Builder与构造器区别、Lombok @Builder、C++实现

大家好,我是你们的老朋友。今天我们来聊聊建造者模式。说实话,这个模式是我在实际项目中用得最频繁的模式之一。为什么?因为它解决了一个很实在的问题——当你的对象构造参数太多、太复杂时,代码会变得很难看。

你想想看,一个类有七八个字段,有些还是可选的。用构造器重载?那得写多少个重载版本?用setter?对象创建和设置分开,中间状态可能不完整。嗯,这时候建造者模式就派上用场了。

什么是建造者模式?

说白了,建造者模式就是把对象的构建过程和它的表示分离开。你一步步地告诉建造者你要什么,最后它给你一个完整的对象。就像去餐厅点餐——你告诉服务员要什么配料,最后厨房给你端出一份完整的汉堡。

我个人习惯把建造者模式理解为「分步构造,一步到位」。它特别适合那些构造参数多、且很多参数有默认值的场景。

核心思想:将复杂对象的构建过程抽象出来,让同样的构建过程可以创建不同的表示。

Builder与构造器的区别

我记得刚入行时,有个同事问我:「直接用构造器不就行了?干嘛要搞个Builder?」这个问题问得好。我们来对比一下:

对比维度 构造器 Builder
参数数量 参数少时好用,多了就乱 参数再多也清晰
可选参数 需要大量重载 天然支持
可读性 new Person("张三", 25, "男", ...) 不知道每个参数含义 Person.builder().name("张三").age(25).gender("男").build() 一目了然
不可变对象 支持(final字段) 支持(Builder构建后对象不可变)
代码量 多(但Lombok可以自动生成)

我曾经在一个项目中接手过一个有15个参数的构造器。每次new对象时,都要数着参数位置,生怕传错。后来我重构成了Builder模式,代码瞬间清爽了。避坑指南:如果你发现构造器参数超过4个,就该考虑用Builder了。

链式调用

链式调用是Builder模式最吸引人的地方。每个setter方法都返回this,这样你就可以连续调用:

// Java示例
User user = new User.Builder()
    .username("admin")
    .password("123456")
    .email("admin@example.com")
    .phone("13800138000")
    .build();

为什么大家喜欢链式调用?因为它符合人的阅读习惯——从上到下,一步步构建。而且IDE的自动补全功能会提示你下一步可以调什么方法,写起来很流畅。

小技巧:链式调用中,每个方法的返回值类型要写对。如果方法返回Builder本身,用return this;如果是子类Builder,要返回子类类型。

Lombok @Builder

说实话,手动写Builder代码挺烦的。尤其是那些字段多的类,Builder代码比业务代码还长。好在Lombok提供了@Builder注解,一行搞定:

import lombok.Builder;
import lombok.ToString;

@Builder
@ToString
public class User {
    private String username;
    private String password;
    private String email;
    private String phone;
    private Integer age;
}

// 使用
User user = User.builder()
    .username("admin")
    .password("123456")
    .email("admin@example.com")
    .build();
System.out.println(user);

Lombok的@Builder会帮你生成一个静态内部Builder类,包含所有字段的setter方法和一个build()方法。我在项目中几乎都用这个,省时省力。但要注意一点:如果你需要自定义构建逻辑(比如字段校验),还是得手写Builder。

注意:Lombok的@Builder默认生成全参构造器。如果你需要无参构造器,可以加上@NoArgsConstructor@AllArgsConstructor。另外,@Builder和@Setter一起用时要注意,Builder的setter方法和普通setter方法可能会冲突。

C++实现

Java有Lombok,C++可没有。但C++的Builder模式实现起来也很优雅。我个人习惯用静态内部类的方式:

#include <iostream>
#include <string>
#include <optional>

class User {
private:
    std::string username_;
    std::string password_;
    std::string email_;
    std::string phone_;
    int age_;

    // 构造器私有,只能通过Builder创建
    User(std::string username, std::string password, 
         std::string email, std::string phone, int age)
        : username_(std::move(username))
        , password_(std::move(password))
        , email_(std::move(email))
        , phone_(std::move(phone))
        , age_(age) {}

public:
    class Builder {
    private:
        std::string username_;
        std::string password_;
        std::string email_;
        std::string phone_;
        int age_ = 0;  // 默认值

    public:
        Builder& setUsername(const std::string& username) {
            username_ = username;
            return *this;
        }

        Builder& setPassword(const std::string& password) {
            password_ = password;
            return *this;
        }

        Builder& setEmail(const std::string& email) {
            email_ = email;
            return *this;
        }

        Builder& setPhone(const std::string& phone) {
            phone_ = phone;
            return *this;
        }

        Builder& setAge(int age) {
            age_ = age;
            return *this;
        }

        User build() {
            // 可以在这里做参数校验
            if (username_.empty()) {
                throw std::runtime_error("username is required");
            }
            return User(username_, password_, email_, phone_, age_);
        }
    };

    friend std::ostream& operator<<(std::ostream& os, const User& user) {
        os << "User{username=" << user.username_ 
           << ", email=" << user.email_ << "}";
        return os;
    }
};

// 使用
int main() {
    User user = User::Builder()
        .setUsername("admin")
        .setPassword("123456")
        .setEmail("admin@example.com")
        .setAge(25)
        .build();
    
    std::cout << user << std::endl;
    return 0;
}

C++版本有几个要点:

  • User的构造器设为private,只能通过Builder创建
  • Builder的每个setter返回Builder&,实现链式调用
  • build()方法中可以做参数校验,确保对象状态合法
  • 使用std::move避免不必要的拷贝

我在一个C++后端项目中就用这种方式重构了配置类的创建。原来有十几个参数,每次new都让人头大。改成Builder后,代码可读性提升了一个档次。

知识体系结构图

下面这张图总结了建造者模式的核心知识点和它们之间的关系:

建造者模式 核心概念 • 分步构建对象 • 构建与表示分离 • 适用于多参数场景 Builder vs 构造器 • 构造器:参数少时好用 • Builder:参数多时清晰 • Builder支持可选参数 链式调用 • 每个setter返回this • 连续调用,流畅阅读 • IDE自动补全友好 Lombok @Builder • 一行注解生成Builder • 省时省力 • 自定义逻辑需手写 C++ 实现 • 静态内部Builder类 • 私有构造器 • build()中做校验

总结

建造者模式不是什么高深的技术,但它解决了一个很实际的问题——让对象创建变得清晰、可控。我个人建议:

  • Java项目优先用Lombok的@Builder,省事
  • C++项目手写Builder,注意用静态内部类和链式调用
  • 参数超过4个时,果断上Builder
  • 需要参数校验或默认值逻辑时,Builder比构造器灵活得多

嗯,今天就聊到这里。建造者模式看似简单,但用好了能让代码质量提升不少。下次你写一个参数很多的类时,不妨试试Builder模式。

公众号:蓝海资料掘金营,微信deep3321