Java中Builder设计模式的必要性及与链式setter的对比
先直接说核心偏差
你想的那种new Person().setXxx()链式写法,本质是给可变对象做了个流畅接口,和经典Builder模式的适用场景差得很远——最核心的问题是,这种写法从语法层面就不可能构造出全final修饰的不可变对象,根本达不到博客里那段Builder代码要实现的效果。
两种写法的直观对比
先把你说的链式set实现贴出来,问题一眼就能看到:
public class Person { // 这里绝对不能加final,因为set方法要在对象new出来之后改属性 private String name; private int age; private String idCard; private String address; // 必须对外暴露无参构造,不然没法new Person()起手 public Person() {} // 每个set返回this实现链式调用 public Person setName(String name) { this.name = name; return this; } public Person setAge(int age) { this.age = age; return this; } public Person setIdCard(String idCard) { this.idCard = idCard; return this; } public Person setAddress(String address) { this.address = address; return this; } // 剩下的getter省略 }
这种写法的使用方式确实和Builder长得像:
Person p = new Person() .setName("张三") .setAge(25) .setIdCard("110xxx") .setAddress("北京");
但你拿到的这个p,是个随时能被改的可变对象,任何地方拿到p的引用,都能再调用p.setAge(-10)给你改个非法值进去,多线程场景下根本没法安全使用。
再看博客里的Builder实现,核心是把“可变的构造过程”和“不可变的最终对象”拆成了两个类:
public class Person { // 所有属性全final,对象初始化完就再也改不了 private final String name; private final int age; private final String idCard; private final String address; // 构造方法私有,外部根本没权限直接new Person private Person(PersonBuilder builder) { this.name = builder.name; this.age = builder.age; this.idCard = builder.idCard; this.address = builder.address; } // 只提供getter,没有任何setter,从根源堵死修改可能 public String getName() {return name;} public int getAge() {return age;} public String getIdCard() {return idCard;} public String getAddress() {return address;} public static class PersonBuilder { // 只有构造阶段用的Builder是可变的,属性不需要加final private String name; private int age; private String idCard; private String address; public PersonBuilder setName(String name) { this.name = name; return this; } public PersonBuilder setAge(int age) { this.age = age; return this; } public PersonBuilder setIdCard(String idCard) { this.idCard = idCard; return this; } public PersonBuilder setAddress(String address) { this.address = address; return this; } public Person build() { // 统一做参数校验,不合法直接抛错,绝对不会生成残缺对象 if (age < 0 || age > 150) throw new IllegalArgumentException("年龄非法"); if (idCard == null || idCard.length() != 18) throw new IllegalArgumentException("身份证号非法"); return new Person(this); } } }
Builder模式比链式set强在哪
- 第一优先级是支持不可变对象:这是你说的链式set方案永远做不到的。不可变对象没有线程安全问题,可以放心当缓存key、在多线程间传递、作为方法返回值,完全不用担心被下游代码篡改,这也是Java后端开发里最推荐的DTO、值对象的写法。
- 构造阶段有统一的校验出口:所有参数校验逻辑都可以放在
build()方法里,只要对象构造成功,就一定是合法的。链式set的写法没有这个统一节点,你完全可以写new Person().setName("张三")就把对象传出去用,漏设了age、idCard都没人拦着,线上跑起来才会抛空指针。 - 语法层面强制必填参数:如果name、idCard是必填项,你可以直接给Builder加带参构造方法
public PersonBuilder(String name, String idCard),用户new Builder的时候不传这两个参数编译就过不去,从根源上避免漏参数。链式set根本做不到这种强制约束。 - 避免半构造对象外泄:Builder模式下,你在set属性的过程中持有的一直是Builder实例,不会对外暴露还没构造完成的Person对象,直到调用build()才会生成最终可用的实例。链式set的写法从new Person()开始拿到的就是Person实例,很可能中途被其他代码拿去调用,拿到的是个属性还没设全的残次品。
当然也不是说链式set完全没用:如果你就是要做一个可变的配置类、参数极少、没有不可变需求,那链式set写起来更简单,完全可以用。但只要涉及不可变值对象、参数多、有校验规则、多线程场景,Builder的优势是链式set替代不了的。
内容的提问来源于stack exchange,提问作者LFC
相关产品推荐
相关产品推荐

