概述
建造者模式(Builder Pattern)是一种创建型设计模式,它允许我们分步骤构建复杂对象。在企业级开发中,建造者模式特别适用于需要创建具有多个可选参数、复杂配置或需要验证的对象场景。
本文将以一个真实的企业项目案例——聊天历史构建器(ChatHistoryBuilder)为例,深入探讨建造者模式在实际开发中的应用。
核心案例:聊天历史构建器
业务背景
在智能客服系统中,我们需要根据不同场景对聊天记录进行格式化处理:
- 过滤掉系统消息和验证消息
- 按时间顺序排序(升序或降序)
- 自定义时间格式
- 支持复杂的自定义过滤逻辑
实现方案
/**
* 聊天历史建造者 - 使用链式调用构建聊天历史
*/
@Slf4j
public class ChatHistoryBuilder {
private List<MessageText> records;
private boolean ascending = true; // 默认升序
private final List<Predicate<MessageText>> filters = new ArrayList<>();
private String dateFormat = "yyyy MM-dd HH:mm:ss";
private ChatHistoryBuilder(List<MessageText> records) {
this.records = new ArrayList<>(records);
}
/**
* 创建建造者实例
*/
public static ChatHistoryBuilder of(List<MessageText> records) {
return new ChatHistoryBuilder(records);
}
/**
* 设置排序方式 - 升序(旧消息在前)
*/
public ChatHistoryBuilder ascending() {
this.ascending = true;
return this;
}
/**
* 设置排序方式 - 降序(新消息在前)
*/
public ChatHistoryBuilder descending() {
this.ascending = false;
return this;
}
/**
* 过滤空消息
*/
public ChatHistoryBuilder filterEmpty() {
filters.add(record -> {
String content = record.getContent();
return content != null && !content.isEmpty();
});
return this;
}
/**
* 过滤验证消息
*/
public ChatHistoryBuilder filterVerification() {
filters.add(record -> {
String content = record.getContent();
return !StrUtil.equals(content, "我通过了你的联系人验证请求,现在我们可以开始聊天了") &&
!StrUtil.equals(content, "我已經添加了你,現在我們可以始聊天了");
});
return this;
}
/**
* 过滤包含指定关键词的消息
*/
public ChatHistoryBuilder filterContains(String... keywords) {
filters.add(record -> {
String content = record.getContent();
if (content == null) return false;
return Arrays.stream(keywords).noneMatch(content::contains);
});
return this;
}
/**
* 自定义过滤器
*/
public ChatHistoryBuilder filter(Predicate<MessageText> customFilter) {
filters.add(customFilter);
return this;
}
/**
* 设置时间格式
*/
public ChatHistoryBuilder withDateFormat(String format) {
this.dateFormat = format;
return this;
}
/**
* 构建聊天历史字符串
*/
public String build() {
if (records == null || records.isEmpty()) {
log.info("没有聊天记录");
return "";
}
// 应用所有过滤器
List<MessageText> filteredRecords = records.stream()
.filter(record -> filters.stream().allMatch(filter -> filter.test(record)))
.collect(Collectors.toList());
// 排序
if (ascending) {
filteredRecords.sort(Comparator.comparingLong(MessageText::getMsgtime));
} else {
filteredRecords.sort((a, b) -> Long.compare(b.getMsgtime(), a.getMsgtime()));
}
// 构建聊天历史
StringBuilder chatHistory = new StringBuilder();
SimpleDateFormat sdf = new SimpleDateFormat(dateFormat);
for (MessageText record : filteredRecords) {
String formattedTime = sdf.format(new Date(record.getMsgtime()));
String senderPrefix = getSenderPrefix(record.getSenderTail());
chatHistory.append(formattedTime).append("\n")
.append(senderPrefix).append(record.getContent()).append("\n");
}
String result = chatHistory.toString();
log.info("聊天历史记录(共{}条,{}序):\n{}", filteredRecords.size(),
ascending ? "升" : "降", result);
return result;
}
}
使用方式
// 业务服务中的实际应用
public String questionClassification(String appId, String chatId) {
// 获取聊天记录
List<MessageText> chatRecords = fetchChatRecords(chatId);
// 使用建造者模式链式调用格式化聊天记录
return ChatHistoryBuilder.of(chatRecords)
.ascending() // 按时间升序排序
.filterEmpty() // 过滤空消息
.filterVerification() // 过滤验证消息
.build();
}
// 其他使用场景
public String getCustomerServiceHistory(List<MessageText> records) {
return ChatHistoryBuilder.of(records)
.descending() // 降序排序
.filterEmpty() // 过滤空消息
.filterContains("系统消息", "自动回复") // 过滤关键词
.withDateFormat("MM-dd HH:mm") // 自定义时间格式
.build();
}
// 复杂自定义过滤
public String getAnalyticsData(List<MessageText> records) {
return ChatHistoryBuilder.of(records)
.ascending()
.filterEmpty()
// 只保留今天的消息
.filter(record -> {
long todayStart = System.currentTimeMillis() - 24 * 60 * 60 * 1000L;
return record.getMsgtime() >= todayStart;
})
// 只保留客户消息
.filter(record -> "@微信".equals(record.getSenderTail()))
.build();
}
案例分析
1. 设计优势
🎯 可读性强
// 链式调用让代码意图非常清晰
ChatHistoryBuilder.of(chatRecords)
.ascending() // 一眼就能看出是升序排序
.filterEmpty() // 明确表示过滤空消息
.filterVerification() // 明确表示过滤验证消息
.build();
🔧 灵活性高
- 可选配置:每个方法都是可选的,可以根据需要组合
- 扩展性好:添加新的过滤器或配置项很容易
- 复用性强:同一个建造者可以用于不同场景
🚀 维护性好
- 职责单一:每个方法只负责一个特定功能
- 易于测试:每个组件可以独立测试
- 易于扩展:新增功能不影响现有代码
2. 关键设计决策
私有构造函数 + 静态工厂方法
private ChatHistoryBuilder(List<MessageText> records) {
this.records = new ArrayList<>(records);
}
public static ChatHistoryBuilder of(List<MessageText> records) {
return new ChatHistoryBuilder(records);
}
- 强制使用静态工厂方法创建实例
- 保证了必要参数的传入
- 提供了清晰的入口点
过滤器集合设计
private final List<Predicate<MessageText>> filters = new ArrayList<>();
- 使用函数式接口支持复杂逻辑
- 支持多个过滤器组合
- 保持了扩展性
不可变性考虑
private ChatHistoryBuilder(List<MessageText> records) {
this.records = new ArrayList<>(records); // 防御性拷贝
}
- 防止外部修改影响构建过程
- 保证了线程安全性
最佳实践
1. 何时使用建造者模式
✅ 适合的场景
- 复杂对象构建:对象有多个可选参数
- 参数验证:需要在构建过程中进行验证
- 不可变对象:希望创建不可变对象
- 可读性要求:需要提高代码可读性
- 配置类对象:有很多配置选项的对象
❌ 不适合的场景
- 简单对象:只有几个必需参数的简单对象
- 性能敏感:对象创建频率极高的场景
- 参数固定:参数组合相对固定的场景
2. 设计原则
流畅接口设计
// 好的设计:方法名清晰表达意图
.ascending()
.filterEmpty()
.withDateFormat("yyyy-MM-dd")
// 不好的设计:方法名不够清晰
.sort(true)
.filter(1)
.format("yyyy-MM-dd")
合理的默认值
public class ChatHistoryBuilder {
private boolean ascending = true; // 提供合理默认值
private String dateFormat = "yyyy MM-dd HH:mm:ss"; // 常用格式
}
参数验证
public ChatHistoryBuilder withDateFormat(String format) {
if (format == null || format.trim().isEmpty()) {
throw new IllegalArgumentException("日期格式不能为空");
}
try {
new SimpleDateFormat(format); // 验证格式有效性
} catch (IllegalArgumentException e) {
throw new IllegalArgumentException("无效的日期格式: " + format);
}
this.dateFormat = format;
return this;
}
3. 与其他模式的结合
建造者 + 工厂模式
public class ChatHistoryBuilderFactory {
public static ChatHistoryBuilder forCustomerService(List<MessageText> records) {
return ChatHistoryBuilder.of(records)
.ascending()
.filterEmpty()
.filterVerification();
}
public static ChatHistoryBuilder forAnalytics(List<MessageText> records) {
return ChatHistoryBuilder.of(records)
.descending()
.filterEmpty()
.filter(record -> "@微信".equals(record.getSenderTail()));
}
}
建造者 + 策略模式
public interface FilterStrategy {
Predicate<MessageText> getFilter();
}
public ChatHistoryBuilder withStrategy(FilterStrategy strategy) {
return filter(strategy.getFilter());
}
性能考虑
1. 对象创建开销
// 优化:复用StringBuilder
private StringBuilder chatHistory = new StringBuilder();
public String build() {
chatHistory.setLength(0); // 清空而不是新建
// ... 构建逻辑
return chatHistory.toString();
}
2. 内存管理
// 大数据量时考虑流式处理
public Stream<String> buildStream() {
return records.stream()
.filter(record -> filters.stream().allMatch(filter -> filter.test(record)))
.sorted(ascending ?
Comparator.comparingLong(MessageText::getMsgtime) :
(a, b) -> Long.compare(b.getMsgtime(), a.getMsgtime()))
.map(this::formatMessage);
}
3. 缓存优化
// 缓存格式化结果
private final Map<String, SimpleDateFormat> formatCache = new ConcurrentHashMap<>();
private SimpleDateFormat getDateFormatter() {
return formatCache.computeIfAbsent(dateFormat, SimpleDateFormat::new);
}
常见陷阱与解决方案
1. 过度设计
问题
// 过度复杂的建造者
ChatHistoryBuilder.of(records)
.withSortOrder(SortOrder.ASCENDING)
.withFilterConfig(FilterConfig.builder()
.emptyFilter(true)
.verificationFilter(true)
.build())
.withFormatterConfig(FormatterConfig.builder()
.dateFormat("yyyy-MM-dd")
.senderPrefixStyle(PrefixStyle.COLON)
.build())
.build();
解决方案
// 保持简洁
ChatHistoryBuilder.of(records)
.ascending()
.filterEmpty()
.filterVerification()
.withDateFormat("yyyy-MM-dd")
.build();
2. 状态管理混乱
问题
// 多次调用同一配置方法导致状态不明确
builder.ascending().descending().ascending(); // 最终是什么顺序?
解决方案
// 明确的状态管理
public ChatHistoryBuilder ascending() {
this.ascending = true;
return this;
}
public ChatHistoryBuilder descending() {
this.ascending = false; // 明确覆盖
return this;
}
3. 线程安全问题
问题
// 共享建造者实例
private static final ChatHistoryBuilder SHARED_BUILDER = ChatHistoryBuilder.of(Collections.emptyList());
解决方案
// 每次创建新实例
public static ChatHistoryBuilder of(List<MessageText> records) {
return new ChatHistoryBuilder(records);
}
总结
建造者模式不是银弹,但在合适的场景下,它能显著提升代码质量和开发效率。关键是要根据实际需求选择合适的实现方式,既不过度设计,也不忽视扩展性需求。