为什么要使用Builder设计模式?

设想一下,我们有一个类,其中代码如下

/**
 * 普通User类:依赖构造器重载
 */
public class User {
    // 属性:包含必填(用户名、密码)和可选(邮箱、手机号、状态)
    private Long id;                  // 可选:数据库自增ID
    private String username;          // 必填:用户名
    private String password;          // 必填:密码
    private String email;             // 可选:邮箱
    private String phone;             // 可选:手机号
    private Integer status;           // 可选:状态(0=禁用,1=启用)

    // -------------------------- 问题1:构造器重载臃肿 --------------------------
    // 构造器1:仅含必填参数(用户名、密码)
    public User(String username, String password) {
        this.username = username;
        this.password = password;
        this.status = 1; // 默认启用
    }

    // 构造器2:必填+邮箱
    public User(String username, String password, String email) {
        this(username, password); // 调用上一个构造器
        this.email = email;
    }

    // 构造器3:必填+邮箱+手机号
    public User(String username, String password, String email, String phone) {
        this(username, password, email);
        this.phone = phone;
    }

    // 构造器4:必填+邮箱+手机号+状态(如果还要加id,得再写构造器)
    public User(String username, String password, String email, String phone, Integer status) {
        this(username, password, email, phone);
        this.status = status;
    }

    //---------------------setter和getter方法
   
}

        可以看到,我们正常的用户账密是必须要填写的,但是像邮箱,电话等是选填的。这时侯就出现了第一个问题,创建对象的时候,我不知道要填写几个对象,所以我就得把所有的构造器全部输出。就显得代码很臃肿

        那么接下来是第二个问题:参数混乱,当我们创建了User user = new User("user","123456",“123123@qq.com”,"123456789")的时候,我们需要记住所有构造参数的前后对应关系,否则很有可能将phone的值填到email,将email的值天道phone

        那么看到这儿,有些小伙伴可能有疑问了,对于问题一,我直接写一个最大的构造器包含所有的参数,后续如果某些没有填写,我用null填充不就可以了吗?

        这样确实解决了表层的问题,但随之而来的是带来的更麻烦的问题。比如我这样

// 创建用户:age和phone未填,传null
User user = new User(1L, "张三", null, "zhangsan@csdn.com", null);

// 隐患:如果email是null(比如用户没填),这行直接抛NullPointerException
int emailLength = user.getEmail().length(); 

// 被迫写大量判空代码,代码臃肿
int safeEmailLength = user.getEmail() != null ? user.getEmail().length() : 0;

        后续我有100处用到User类,我就得使用100个if来判断是否为null,是不是非常麻烦。

所以这就引出了我们的Builder设计模式

什么是Builder设计模式

        官方的定义为:将一个复杂对象的构建与它的表示分离,使得同样的构建过程可以创建不同的表示。 

        看着有点迷糊吧,没关系,接着往下看肯定就通透了。

        我先直接给出Builder创建上述User类的代码,随后我们一点一点分析。

/**
 * 用户实体类(带校验的手动 Builder)
 */
public class User {
    // 核心属性(final 保证不可变,避免创建后被修改)
    private final Long id;
    private final String username;  // 必填
    private final String password;  // 必填
    private final String nickname;  // 可选(默认=username)
    private final String email;     // 可选(需格式校验)
    private final String phone;     // 可选(需格式校验)
    private final Integer status;   // 可选(默认=1)

    // 1. 私有构造器:仅允许内部 Builder 调用(避免外部直接 new)
    private User(Builder builder) {
        this.id = builder.id;
        this.username = builder.username;
        this.password = builder.password;
        // 处理默认值:nickname 未传时,默认等于 username
        this.nickname = (builder.nickname == null || builder.nickname.isEmpty()) 
                        ? builder.username 
                        : builder.nickname;
        this.email = builder.email;
        this.phone = builder.phone;
        // 处理默认值:status 未传时,默认=1(启用)
        this.status = builder.status;
    }

    // 2. Getter 方法(无 Setter,保证对象不可变)
    public Long getId() { return id; }
    public String getUsername() { return username; }
    public String getPassword() { return password; }
    public String getNickname() { return nickname; }
    public String getEmail() { return email; }
    public String getPhone() { return phone; }
    public Integer getStatus() { return status; }

    // 3. 核心:手动实现 Builder(含完整校验逻辑)
    public static class Builder {
        // 3.1 必选属性:必须在 Builder 构造器中传入(强制必填)
        private final String username;
        private final String password;
        // 3.2 可选属性:默认值为 null 或默认值
        private Long id;
        private String nickname;
        private String email;
        private String phone;
        private Integer status;

       
        // 3.3 Builder 构造器:仅接收必填属性,同时做校验
        public Builder(String username, String password) {
            this.username = username;
            this.password = password;
        }

        // 3.4 可选属性:id
        public Builder id(Long id) {
            this.id = id;
            return this; // 链式调用核心:返回自身
        }

        // 3.5 可选属性:nickname
        public Builder nickname(String nickname) {
            this.nickname = nickname.trim();
            return this;
        }

        // 3.6 可选属性:email
        public Builder email(String email) {
            this.email = email;
            return this;
        }

        // 3.7 可选属性:phone
        public Builder phone(String phone) {
            this.phone = phone;
            return this;
        }

        // 3.8 可选属性:status
        public Builder status(Integer status) {
            this.status = status;
            return this;
        }

        // 3.9 构建方法:返回最终 User 对象(可加最终校验)
        public User build() {
            return new User(this);
        }
    }

}

可能代码有点多,让我来逐一为大家解释一下困惑:

1. 首先是构造器变为私有的了,因为我们要避免外部有User user = new User(...)这种情况,具体原因我们开头已经讲过了

2. 其次就是我创建了一个内部静态类Builder,里边的像username和password这种一旦赋值后续不可变属性都被final修饰了。

3. 随后是通过Builder构造器,内部仅接受必填属性。

4. 随后是一群设置方法,但值得注意的是,我们这里的功能虽然类似于之前的setter方法,但与之不同的,一是函数名直接使用属性名,这样做是为了解决我们之前提到的属性名混乱的问题(比如直接使用phone("123456"),是不是比setPhone("123456")好多了). 二是返回值为Builder.这样就可以实现链式调用,如下代码

User.Builder("user","123")
    .email("123456") //此时返回值对象是不是Builder,所以我们还可以接着调用
    .phone("456789") //以此类推

我想特别说明的是,这里eamil和phone的设置其实完全可以反转的,这也就解决了问题“顺序记忆混乱”

5. 有一个返回值类型为User的build()方法,因为我们4.中提到的所有最后返回值是Builder类型而非我们需要的User类型,所以我们需要一个build方法来结束

综上,User user = User.Builder("user","123").email("123").phone("123456879").build();就是最终使用技巧

!!!看到这儿,有人可能会说“这不就是@Builder注解嘛”

这样确实有点像。 但是我想说的是,在我们实际的过程中,大多数数据是需要验证的。比如手机号的校验逻辑,年龄的校验逻辑,性别的校验逻辑等等,这些都是需要我们自己在方法中定义的,总不可能以后我每次创建一个对象,先使用一大堆逻辑对他属性进行校验吧,那样不仅代码臃肿而且非常不优雅。而@Builder却只能草草的像之前的代码一样你就算手机号填“1”,email填“1”也能录取进去。

我们上述的所有代码都是未经过校验的,所以理论上代码质量是不过关的。

实际应该是这样子的:

/**
 * 用户实体类
 */
public class User {
    // 核心属性(final 保证不可变,避免创建后被修改)
    private final Long id;
    private final String username;  // 必填
    private final String password;  // 必填
    private final String nickname;  // 可选(默认=username)
    private final String email;     // 可选(需格式校验)
    private final String phone;     // 可选(需格式校验)
    private final Integer status;   // 可选(默认=1)

    // 1. 私有构造器:仅允许内部 Builder 调用(避免外部直接 new)
    private User(Builder builder) {
        this.id = builder.id;
        this.username = builder.username;
        this.password = builder.password;
        // 处理默认值:nickname 未传时,默认等于 username
        this.nickname = (builder.nickname == null || builder.nickname.isEmpty()) 
                        ? builder.username 
                        : builder.nickname;
        this.email = builder.email;
        this.phone = builder.phone;
        // 处理默认值:status 未传时,默认=1(启用)
        this.status = (builder.status == null) ? 1 : builder.status;
    }

    // 2. Getter 方法(无 Setter,保证对象不可变)
    public Long getId() { return id; }
    public String getUsername() { return username; }
    public String getPassword() { return password; }
    public String getNickname() { return nickname; }
    public String getEmail() { return email; }
    public String getPhone() { return phone; }
    public Integer getStatus() { return status; }

    // 3. 核心:手动实现 Builder(含完整校验逻辑)
    public static class Builder {
        // 3.1 必选属性:必须在 Builder 构造器中传入(强制必填)
        private final String username;
        private final String password;
        // 3.2 可选属性:默认值为 null 或默认值
        private Long id;
        private String nickname;
        private String email;
        private String phone;
        private Integer status;

        // 3.3 邮箱/手机号校验正则(业务规则固化)
        private static final Pattern EMAIL_PATTERN = Pattern.compile(
                "^[A-Za-z0-9+_.-]+@[A-Za-z0-9.-]+$"
        );
        private static final Pattern PHONE_PATTERN = Pattern.compile(
                "^1[3-9]\\d{9}$"
        );

        // 3.4 Builder 构造器:仅接收必填属性,同时做校验
        public Builder(String username, String password) {
            // 校验 username:非空 + 长度 2~20
            if (username == null || username.isEmpty()) {
                throw new IllegalArgumentException("用户名不能为空!");
            }
            if (username.length() < 2 || username.length() > 20) {
                throw new IllegalArgumentException("用户名长度必须为 2~20 字符(当前:" + username.length() + ")");
            }
            this.username = username;

            // 校验 password:非空 + 长度 6~20
            if (password == null || password.isEmpty()) {
                throw new IllegalArgumentException("密码不能为空!");
            }
            if (password.length() < 6 || password.length() > 20) {
                throw new IllegalArgumentException("密码长度必须为 6~20 字符(当前:" + password.length() + ")");
            }
            this.password = password;
        }

        // 3.5 可选属性:id(无校验,数据库自增)
        public Builder id(Long id) {
            this.id = id;
            return this; // 链式调用核心:返回自身
        }

        // 3.6 可选属性:nickname(无强制校验,仅去空)
        public Builder nickname(String nickname) {
            this.nickname = (nickname == null) ? null : nickname.trim();
            return this;
        }

        // 3.7 可选属性:email(非空时校验格式)
        public Builder email(String email) {
            if (email != null && !email.isEmpty()) {
                if (!EMAIL_PATTERN.matcher(email).matches()) {
                    throw new IllegalArgumentException("邮箱格式非法(示例:xxx@xx.com):" + email);
                }
            }
            this.email = email;
            return this;
        }

        // 3.8 可选属性:phone(非空时校验格式)
        public Builder phone(String phone) {
            if (phone != null && !phone.isEmpty()) {
                if (!PHONE_PATTERN.matcher(phone).matches()) {
                    throw new IllegalArgumentException("手机号格式非法(需11位有效手机号):" + phone);
                }
            }
            this.phone = phone;
            return this;
        }

        // 3.9 可选属性:status(非空时校验值范围)
        public Builder status(Integer status) {
            if (status != null) {
                if (status != 0 && status != 1) {
                    throw new IllegalArgumentException("用户状态非法(仅支持 0=禁用,1=启用):" + status);
                }
            }
            this.status = status;
            return this;
        }

        // 3.10 构建方法:返回最终 User 对象(可加最终校验)
        public User build() {
            // 可选:最终校验(如“若传了手机号,必须传邮箱”——根据业务灵活加)
            // if (phone != null && email == null) {
            //     throw new IllegalStateException("传入手机号时,必须同时传入邮箱!");
            // }
            return new User(this);
        }
    }

    @Override
    public String toString() {
        return "User{" +
                "id=" + id +
                ", username='" + username + '\'' +
                ", password='" + password + '\'' +
                ", nickname='" + nickname + '\'' +
                ", email='" + email + '\'' +
                ", phone='" + phone + '\'' +
                ", status=" + status +
                '}';
    }
}

总结

好啦,相信大家看到这儿,应该理解什么是Builder设计模式了吧。通俗点来讲就是:创建对象时,用到哪个参数就填哪个(没用到的不用管,省得传 null、记顺序),填每个参数的方法都会返回 Builder 自己(这样能连着填,不用写一堆行),最后点一下 build (),就能拿到一个造好、且造好后就改不了的对象 —— 核心是为了填参数时不麻烦、不犯错,造出来的对象还安全。

Logo

开源鸿蒙跨平台开发社区汇聚开发者与厂商,共建“一次开发,多端部署”的开源生态,致力于降低跨端开发门槛,推动万物智联创新。

更多推荐