智能客服场景下的建造者模式实战:构建灵活可配的聊天历史处理器

0 阅读6分钟

概述

建造者模式(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);
}

总结

建造者模式不是银弹,但在合适的场景下,它能显著提升代码质量和开发效率。关键是要根据实际需求选择合适的实现方式,既不过度设计,也不忽视扩展性需求。