设计模式之Builder
为什么要使用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 (),就能拿到一个造好、且造好后就改不了的对象 —— 核心是为了填参数时不麻烦、不犯错,造出来的对象还安全。
更多推荐



所有评论(0)