Java 策略模式 + 聚合对象:实现多模块的统计与聚合,快速扩展的实战

本文面向后端开发者,介绍如何用**策略模式(Strategy)配合聚合对象(Aggregate Object)**来实现多模块(如订单、用户、页面、支付等)的统计与聚合。重点放在可扩展性与工程化实践:如何在不修改核心代码的情况下,快速新增统计模块或聚合规则。


场景与痛点

在大型系统中,经常需要对不同模块做"统计/聚合" ------ 比如:

  • 订单模块:统计日订单数、总金额、平均客单价

  • 用户模块:活跃用户数、新增用户数

  • 页面模块:PV/UV/平均停留时长

传统实现往往是每个模块单独写统计逻辑,导致:

  1. 代码重复,难以维护;

  2. 新增统计需求需要改动多处代码;

  3. 聚合规则耦合到业务逻辑,扩展难。

目标:使用策略模式抽离统计规则与聚合实现,使用聚合对象统一承载中间结果与最终结果,使得新增模块或统计规则只需注册新的策略/聚合器即可,无需改动核心运行流程。


设计思路

总体思路:

  1. 定义统计策略接口(StatisticStrategy ,每个模块实现自己的统计逻辑,把统计项抽象成统一的 MetricAggregateObject

  2. 定义聚合器(Aggregator 负责合并来自多条数据的部分统计结果。

  3. 使用策略注册(通过配置或 Spring 自动扫描)把模块策略放入容器,运行时根据模块类型或策略类型动态选择并执行。

  4. 所有统计结果统一返回 AggregateResult(或 DTO),便于展示或输出到 BI/ES/Kafka。

组件关系示意:

复制代码
请求/消息 -> 策略选择 -> 处理单条数据 -> 生成部分聚合对象 -> 聚合器合并 -> 最终结果

关键接口设计

下面给出核心接口:

java 复制代码
// 统计策略:处理单条业务数据,返回一个部分聚合对象
public interface StatisticStrategy<T> {
    /**
     * strategyKey 用来标识该策略(模块或统计项)
     */
    String name();

    /**
     * 处理单条业务数据,返回部分聚合结果
     */
    AggregateObject handle(T bizObject);
}

// 聚合对象:携带可被合并的字段
public class AggregateObject {
    private String key; // 比如按日期、维度、模块等
    private Map<String, Number> metrics = new HashMap<>();

    // 方便构造与操作
    public void addMetric(String name, Number value) {
        metrics.merge(name, value, (a, b) -> a.doubleValue() + b.doubleValue());
    }

    // getters/setters omitted
}

// 聚合器:将多个 AggregateObject 合并为一个
public interface Aggregator {
    AggregateObject aggregate(AggregateObject a, AggregateObject b);
}

// 最终返回的结果 DTO
public class AggregateResult {
    private String key;
    private Map<String, Number> metrics;

    // constructor/getters
}

示例:实现两个模块的策略

举例实现 OrderStatisticStrategyPageViewStatisticStrategy

java 复制代码
// 订单统计策略
public class OrderStatisticStrategy implements StatisticStrategy<Order> {

    @Override
    public String name() { return "order"; }

    @Override
    public AggregateObject handle(Order order) {
        AggregateObject obj = new AggregateObject();
        obj.setKey(order.getDate()); // 假设按天聚合
        obj.addMetric("order_count", 1);
        obj.addMetric("order_amount", order.getAmount());
        return obj;
    }
}

// 页面 PV 统计策略
public class PageViewStatisticStrategy implements StatisticStrategy<PageView> {
    @Override
    public String name() { return "pageview"; }

    @Override
    public AggregateObject handle(PageView pv) {
        AggregateObject obj = new AggregateObject();
        obj.setKey(pv.getDate());
        obj.addMetric("pv", 1);
        obj.addMetric("uv", pv.isUnique() ? 1 : 0);
        return obj;
    }
}

聚合器实现

实现一个简单的 SumAggregator,对相同 key 的 AggregateObject 做数值求和:

java 复制代码
public class SumAggregator implements Aggregator {
    @Override
    public AggregateObject aggregate(AggregateObject a, AggregateObject b) {
        if (a == null) return b;
        if (b == null) return a;
        // 假设 key 相同,合并 metrics
        for (Map.Entry<String, Number> e : b.getMetrics().entrySet()) {
            a.addMetric(e.getKey(), e.getValue());
        }
        return a;
    }
}

策略注册与执行器

为了方便扩展,把策略注册到 StrategyRegistry,并提供一个 StatisticEngine 来调度。

java 复制代码
public class StrategyRegistry {
    private final Map<String, StatisticStrategy<?>> map = new ConcurrentHashMap<>();

    public void register(StatisticStrategy<?> strategy) {
        map.put(strategy.name(), strategy);
    }

    public StatisticStrategy<?> get(String name) { return map.get(name); }
}

public class StatisticEngine {
    private final StrategyRegistry registry;
    private final Aggregator aggregator;

    public StatisticEngine(StrategyRegistry registry, Aggregator aggregator) {
        this.registry = registry;
        this.aggregator = aggregator;
    }

    // 处理一批业务对象(不同类型可通过 wrapper 指定策略名和对象)
    public AggregateResult handleBatch(String strategyName, List<?> bizObjects) {
        StatisticStrategy strategy = registry.get(strategyName);
        if (strategy == null) throw new IllegalArgumentException("No strategy: " + strategyName);

        // 按 key 聚合
        Map<String, AggregateObject> map = new HashMap<>();
        for (Object o : bizObjects) {
            AggregateObject part = strategy.handle(o);
            map.merge(part.getKey(), part, aggregator::aggregate);
        }

        // 暂只返回按 key 合并后的单条结果(可扩展为 list)
        // 这里只演示聚合为单个合并对象
        AggregateObject merged = map.values().stream().reduce(aggregator::aggregate).orElse(null);
        return new AggregateResult(merged.getKey(), merged.getMetrics());
    }
}

说明:以上代码演示了核心流程。生产级系统可将 strategyName 与消息的 module 字段一一对应,或者把策略实现成 Spring Bean 并通过注解扫描自动注册。


扩展性与快速新增模块的步骤

要新增一个模块统计:

  1. 新建一个实现 StatisticStrategy<T> 的类。

  2. 实现 handle(T) 返回 AggregateObject(只需关注该模块需要的指标)。

  3. 把策略注册到 StrategyRegistry(或在 Spring 中声明为 Bean 并自动注入)。

  4. 在数据流入口(消息队列、HTTP、定时任务等)指定该策略名或 module,交由 StatisticEngine 处理。

因为业务逻辑与聚合框架解耦,新增模块不需改动引擎或聚合器代码,只需单独增加策略实现。


进阶优化建议(工程级考虑)

  1. 使用泛型与类型安全 :当前示例为了简洁使用了 List<?> 和类型擦除,生产可结合 Class<T> 与泛型做更严格的类型校验。

  2. 异步/批处理:在高吞吐场景下,使用消息队列(Kafka/RocketMQ)把业务事件发送到统计服务,统计服务按批次聚合并写入 OLAP 存储。

  3. 维度化设计AggregateObjectkey 可支持多维(例如 date:region),或者拆成 List<Dimension> 结构,便于做复杂分组。

  4. 可配置的策略路由:通过配置(YAML/DB)控制策略启用/停用或路由规则,无需重新部署代码。

  5. 监控与降级:策略执行应有埋点与限流策略,避免单个策略异常拖垮整体统计。

  6. 扩展聚合器 :除了 SumAggregator,还可实现 MaxAggregatorAverageAggregator(需维护 count)等。


完整示例项目结构(建议)

复制代码
com.example.stats
├── engine
│   ├── StatisticEngine.java
│   └── StrategyRegistry.java
├── strategy
│   ├── StatisticStrategy.java
│   ├── OrderStatisticStrategy.java
│   └── PageViewStatisticStrategy.java
├── aggregator
│   ├── Aggregator.java
│   └── SumAggregator.java
├── dto
│   ├── AggregateObject.java
│   └── AggregateResult.java
└── app
    └── Application.java

小结

本文介绍了如何使用策略模式结合聚合对象来构建一个可扩展的多模块统计聚合框架。核心思想是:把每个模块的统计逻辑封装成策略,把可合并的中间结果封装为聚合对象,再由聚合器统一合并。这样可以在不改动核心引擎的情况下,快速新增统计模块或聚合规则。

相关推荐
是店小二呀1 小时前
openGauss进阶:使用DBeaver可视化管理与实战
开发语言·人工智能·yolo
万粉变现经纪人1 小时前
如何解决 pip install 编译报错 ‘cl.exe’ not found(缺少 VS C++ 工具集)问题
开发语言·c++·人工智能·python·pycharm·bug·pip
U***e631 小时前
JavaScript数据分析
开发语言·javascript·数据分析
h***59332 小时前
SpringBoot中如何手动开启事务
java·spring boot·spring
倚肆2 小时前
Java泛型详解:尖括号<>、通配符?与类型参数T
java
韩风6662 小时前
雪花id改多workerID依赖redis
java
BD_Marathon2 小时前
Eclipse 代码自动补全设置
android·java·eclipse
L.EscaRC2 小时前
深入解析SpringBoot中的循环依赖机制与解决方案
java·spring boot·spring·循环依赖
Cx330❀2 小时前
C++ map 全面解析:从基础用法到实战技巧
开发语言·c++·算法