可批量创建多对象的Java Builder模式如何优化消除重复方法?
批量构建对象的Builder模式优化方案
现有设计的问题
你的核心设计思路是合理的,本质是在常规Builder的基础上扩展了批量实例的差异化/统一赋值能力,已经覆盖了单对象构建、批量同值构建、批量按索引差异化构建三个核心场景,能满足功能需求。但现有实现存在几个可优化点,还有几个会导致运行报错的bug:
- 初始化逻辑缺失:
user()、users(int)两个构造入口都没有初始化内部的users列表,调用withXxx方法时会直接抛空指针;users(int)也没有提前创建对应数量的User实例 - 代码笔误:方法名存在
withFirstname/withFirstName大小写不一致的问题,build方法里引用的user变量不存在,应该是users - 冗余代码多:每个属性都要写固定值、Function入参两个重载,每个重载里都要写一遍遍历
users列表的循环,属性越多重复代码量越高 - 无意义的拷贝构造方法:现有逻辑里没有用到拷贝构造的场景,属于冗余代码
低侵入优化方案(无需注解处理器)
不需要上注解这类重方案,只要调整代码结构就能消除90%的冗余,同时修复现有bug:
- 抽离公共遍历赋值逻辑,所有属性设置方法复用同一段循环逻辑
- 删掉所有固定值入参的重载方法,提供一个通用的常量转换工具方法,固定值场景统一用这个方法包装即可,不用为每个属性单独写重载
- 补全实例初始化逻辑,统一入口避免重复代码
优化后的核心代码如下:
package builders; import java.util.ArrayList; import java.util.List; import java.util.function.BiConsumer; import java.util.function.Function; public class UserBuilder { private List<User> users; private UserBuilder() {} // 单对象构建入口,直接复用批量构建逻辑 public static UserBuilder user() { return users(1); } // 批量构建入口,统一完成实例初始化 public static UserBuilder users(int userCount) { if (userCount < 1) { throw new IllegalArgumentException("构建数量必须大于0"); } UserBuilder builder = new UserBuilder(); builder.users = new ArrayList<>(userCount); for (int i = 0; i < userCount; i++) { builder.users.add(new User()); } return builder; } // 通用常量包装工具,替代所有固定值重载方法 public static <T> Function<Integer, T> constant(T value) { return unused -> value; } // 抽离公共的批量赋值逻辑,所有with方法都复用 private UserBuilder setProperty(BiConsumer<User, Integer> setter) { for (int i = 0; i < users.size(); i++) { setter.accept(users.get(i), i); } return this; } // 属性设置方法只需要写Function入参的版本,不用再写固定值重载 public UserBuilder withFirstName(Function<Integer, String> func) { return setProperty((user, idx) -> user.setFirstName(func.apply(idx))); } public UserBuilder withLastName(Function<Integer, String> func) { return setProperty((user, idx) -> user.setLastName(func.apply(idx))); } public User build() { return users.get(0); } public User build(int index) { return users.get(index); } public List<User> buildAll() { return new ArrayList<>(users); // 返回副本避免外部修改内部状态 } }
优化后的调用方式和之前几乎没有区别,只是固定值场景需要套一层constant:
// 单对象构建 User user = UserBuilder.user() .withFirstName(constant("toto")) .withLastName(constant("tata")) .build(); // 批量构建 List<User> users = UserBuilder.users(10) .withFirstName(index -> "toto" + index) .withLastName(constant("tata")) .buildAll();
如果觉得constant写法不够简洁,也可以选择保留固定值入参的重载,但因为已经抽离了核心逻辑,重载的代码量非常小,每个方法只需要一行:
public UserBuilder withFirstName(String firstName) { return withFirstName(constant(firstName)); }
比之前每个重载都要写遍历循环的冗余度低很多。
注解处理器方案(适合大量实体类的场景)
如果你的项目里有大量实体类都需要这种批量Builder能力,手写所有with方法还是有成本,可以用注解处理器在编译期自动生成代码:
- 自定义
@BulkBuilder注解标记需要生成批量Builder的实体类 - 写APT处理器在编译期扫描实体类的字段,自动生成对应的with方法(包含Function入参版本、固定值入参版本)、build方法、批量构建入口
这种方案零手写重复代码,但实现成本较高,适合做通用工具框架的时候用,普通业务场景用上面的低侵入优化方案足够。
内容的提问来源于stack exchange,提问作者Theo Cerutti
相关产品推荐
相关产品推荐

