原创|一个统计查询模块基于设计模式的抽象设计

news2024/11/29 22:40:26

文章目录

  • 一、需求背景
  • 二、详细设计
    • UML设计
    • 包设计
  • 三、程序设计
    • 1、VideoAdStatCaliberEnum
    • 2、LiveDashboardBusiness
    • 3、StatHandleDispatcher
    • 4、StatCaliberEnum
    • 5、StatContext
    • 5、AbstractStatHandler
    • 6、LoggerService
    • 7、AbstractVideoAdStatHandler
      • 1、VideoAdStatContext
      • 2、VideoAdStatByDailyHandler
      • 3、VideoAdStatByJobNumberHandler
  • 四、总结

我们经常会遇到形形色色的产品需求,在快速的迭代中,我们设计的代码会变得越来越臃肿。之所以会这样,来源于我们没有更好的抽象设计,仅仅是基于ControllerServiceDAO三层分层设计,我们把更多的业务逻辑代码通过一个个方法堆积在Service层。我相信大家心有体会,这个Service伴随着业务迭代会越来越多的代码。试想一下,我们应该怎么可以更好的抽象设计,来达到避免这种现象产生呢。

一、需求背景

有个需求,从交互上就是两种统计口径。
第一种口径就是:曝光日期+直播间id
第二种口径就是:曝光日期+直播间id+职位编号。
本质上这两种统计口径其实业务处理逻辑有相似之处,但亦有差异之处,我们怎么设计才能更好的复用代码呢?同时后续再有其他场景的统计也可以进而复用这部分抽象设计呢,鉴于此,我通过思考这种问题,有了本篇文章。

二、详细设计

UML设计

UML设计

包设计

整个模块包划分,后续新增场景只需要在handler子包增加子包,如果某个场景增加统计口径只需要在对应场景子包中新增子类Handler即可。我们可以看出一个子包,只需要创建一个上下文类来继承基类,同时新增Hander来负责具体的业务处理。每个子类做到职责单一,符合开闭原则。
在这里插入图片描述

三、程序设计

1、VideoAdStatCaliberEnum

定义一个枚举,通过这个枚举对外暴露内部支持的业务场景。

/**
 * 视频广告数据看板统计口径
 *
 * @author : 石冬冬-Sieg Heil
 * @since 2022/11/30 9:39 AM
 */
@Getter
@ThriftStruct
public enum VideoAdStatCaliberEnum {
    /**
     * 未知
     */
    UNKNOWN(-1, "未知"),
    /**
     * 按天的明细数据
     */
    BY_DAILY(1, "按天的明细数据"),
    /**
     * 按岗位的明细数据
     */
    BY_JOB_NUMBER(2, "按岗位的明细数据"),
    ;

    @ThriftField(1)
    private final int value;

    private final String name;

    VideoAdStatCaliberEnum(Integer value, String name) {
        this.value = value;
        this.name = name;
    }

    public static VideoAdStatCaliberEnum valueOf(Integer value) {
        for (VideoAdStatCaliberEnum each : VideoAdStatCaliberEnum.values()) {
            if (each.getValue() == value) {
                return each;
            }
        }
        return VideoAdStatCaliberEnum.UNKNOWN;
    }
}

2、LiveDashboardBusiness

对外暴露的业务类,相当于一个门面。

/**
 * @author : 石冬冬-Sieg Heil
 * @since 2022/11/29 4:02 PM
 */
@Service
public class LiveDashboardBusiness {

    @Autowired
    private StatHandleConverter statHandleConverter;

    @Autowired
    private StatHandleDispatcher statHandlerDispatcher;

    /**
     * 获取视频广告数据看板
     * <p>
     * 可以参考:{@link com.zhaopin.c.live.operation.business.LiveRoomBusiness#getDashboard(DashboardRequestBO)}
     *
     * @param request 请求参数
     * @return 返回值
     */
    public VideoAdDashboardBO getVideoAdDashboard(VideoAdDashboardRequestBO request) {
        VideoAdStatCaliberEnum statCaliber = VideoAdStatCaliberEnum.valueOf(request.getCaliber());
        if (Objects.equals(VideoAdStatCaliberEnum.UNKNOWN, statCaliber)) {
            throw new ForbiddenException("非法参数,statCaliber={}", statCaliber.getName());
        }
        StatCaliberEnum caliber = null;
        if (Objects.equals(VideoAdStatCaliberEnum.BY_DAILY, statCaliber)) {
            caliber = StatCaliberEnum.VIDEO_AD_BY_DAILY;
        }
        if (Objects.equals(VideoAdStatCaliberEnum.BY_JOB_NUMBER, statCaliber)) {
            caliber = StatCaliberEnum.VIDEO_AD_BY_JOB_NUMBER;
        }
        VideoAdStatContext context = statHandleConverter.convertToVideoAdStatContext(caliber, request);
        statHandlerDispatcher.execute(context);
        VideoAdDashboardBO response = context.getResponse();
        return response;
    }
}

3、StatHandleDispatcher

统计处理的路由分发器,该类封装了一系列处理类集合。

/**
 * 统计处理分发器
 *
 * @author : 石冬冬-Sieg Heil
 * @since 2022/11/30 10:00 AM
 */
@Component
public class StatHandleDispatcher {

    @Autowired
    private List<AbstractStatHandler> handlers;

    /**
     * 分发处理
     *
     * @param context 统计上下文对象
     */
    public void execute(StatContext context) {
        handlers.stream()
            .filter(each -> Objects.equals(each.getStatCaliber(), context.getStatCaliber()))
            .forEach(each -> each.execute(context));
    }
}

4、StatCaliberEnum

具体子包场景模块的统计口径枚举,该类为作为具体场景模块的一个成员属性。

/**
 * 数据看板统计口径
 *
 * @author : 石冬冬-Sieg Heil
 * @since 2022/11/30 9:39 AM
 */
@Getter
public enum StatCaliberEnum {
    /**
     * (视频广告)按天的明细数据
     */
    VIDEO_AD_BY_DAILY(1, "按天的明细数据"),
    /**
     * (视频广告)按岗位的明细数据
     */
    VIDEO_AD_BY_JOB_NUMBER(2, "按岗位的明细数据"),
    ;

    private final int value;

    private final String name;

    StatCaliberEnum(Integer value, String name) {
        this.value = value;
        this.name = name;
    }
}

5、StatContext

统计上下文对象,封装了内部处理所依赖的请求参数,以及输出的统计结果,该类是一个泛型基类,需要具体场景来继承该类。


/**
 * 统计上下文对象
 *
 * @author : 石冬冬-Sieg Heil
 * @since 2022/11/30 9:46 AM
 */
@ToString
@Getter
@Setter
public class StatContext<Request, Response> {
    /**
     * 统计口径
     */
    private StatCaliberEnum statCaliber;
    /**
     * 请求参数
     */
    private Request request;
    /**
     * 统计结果
     */
    private Response response;
}

5、AbstractStatHandler

统计处理类,所有子类需要继承该抽象类。

/**
 * 抽象统计处理器
 *
 * @author : 石冬冬-Sieg Heil
 * @since 2022/11/30 9:46 AM
 */
public abstract class AbstractStatHandler<Context extends StatContext> implements LoggerService {
    /**
     * 统计口径
     */
    protected StatCaliberEnum statCaliber;

    /**
     * 构造函数
     *
     * @param statCaliber 统计口径
     */
    public AbstractStatHandler(StatCaliberEnum statCaliber) {
        this.statCaliber = statCaliber;
    }

    /**
     * 公共方法
     *
     * @param context 上下文对象
     */
    public void execute(Context context) {
        if (logDebug()) {
            getLog().info("[StatHandle]req={}", JsonUtils.toJson(context.getRequest()));
        }
        doHandle(context);
        if (logDebug()) {
            getLog().info("[StatHandle]res={}", JsonUtils.toJson(context.getResponse()));
        }
    }

    /**
     * 处理
     *
     * @param context 上下文对象
     */
    protected abstract void doHandle(Context context);

    public StatCaliberEnum getStatCaliber() {
        return statCaliber;
    }
}

6、LoggerService

日志DEBUG服务接口,相关子类可以实现该接口,来通过配置进而控制日志debug输出。

import org.slf4j.Logger;
/**
 * 日志DEBUG服务接口
 *
 * @author : 石冬冬-Sieg Heil
 * @since 2022/11/29 11:40 AM
 */
public interface LoggerService {
    /**
     * 日志门面
     *
     * @return 日志门面
     */
    Logger getLog();

    /**
     * 是否启用日志输出
     *
     * @return 是否启用日志输出
     */
    default boolean logDebug() {
        return true;
    }
}

7、AbstractVideoAdStatHandler

具体场景模块的抽象类

/**
 * 抽象 视频广告统计处理器
 *
 * @author : 石冬冬-Sieg Heil
 * @since 2022/11/30 10:20 AM
 */
public abstract class AbstractVideoAdStatHandler extends AbstractStatHandler<VideoAdStatContext> {

    @Autowired
    protected LiveDashboardConverter liveDashboardConverter;

    @Autowired
    protected ThirdLiveRoomBusiness liveRoomBusiness;

    @Autowired
    protected ThirdVideoAdEffectBusiness thirdVideoAdEffectBusiness;

    /**
     * 构造函数
     *
     * @param statCaliber 统计口径
     */
    public AbstractVideoAdStatHandler(StatCaliberEnum statCaliber) {
        super(statCaliber);
    }

    @Override
    protected void doHandle(VideoAdStatContext context) {
        VideoAdDashboardRequestBO request = context.getRequest();
        Long roomId = request.getRoomId();
        RoomBasicInfoBO basicInfo = liveRoomBusiness.getRoomByRoomId(roomId);
        if (Objects.isNull(basicInfo)) {
            throw new NotExistException("直播间不存在[" + roomId + "]", JsonUtils.toJson(request));
        }
        if (!Objects.equals(ProductTypeEnum.VIDEO_AD.getValue(), basicInfo.getProductType())) {
            throw new ServerException("非视频广告直播间[" + roomId + "]", JsonUtils.toJson(request));
        }
        VideoAdEffectBO adEffectBO = doQueryEffect(request);
        VideoAdDashboardBO dashboard = liveDashboardConverter.convertToVideoAdDashboardBO(adEffectBO, request, basicInfo);
        context.setResponse(dashboard);
    }

    /**
     * 统计查询处理
     *
     * @param request 请求参数
     * @return 统计结果
     */
    abstract VideoAdEffectBO doQueryEffect(VideoAdDashboardRequestBO request);
}

1、VideoAdStatContext

视频广告统计上下文对象

/**
 * 视频广告统计上下文对象
 *
 * @author : 石冬冬-Sieg Heil
 * @since 2022/11/30 9:49 AM
 */
@ToString(callSuper = true)
@Getter
@Setter
public class VideoAdStatContext extends StatContext<VideoAdDashboardRequestBO, VideoAdDashboardBO> {
    /**
     * 请求参数
     */
    private VideoAdDashboardRequestBO request;
    /**
     * 统计结果
     */
    private VideoAdDashboardBO response;
}

2、VideoAdStatByDailyHandler

具体场景模块的子类

/**
 * 视频广告统计处理器(按天)
 *
 * @author : 石冬冬-Sieg Heil
 * @since 2022/11/30 9:55 AM
 */
@Service
@Slf4j
public class VideoAdStatByDailyHandler extends AbstractVideoAdStatHandler {

    /**
     * 构造函数
     */
    public VideoAdStatByDailyHandler() {
        super(StatCaliberEnum.VIDEO_AD_BY_DAILY);
    }

    @Override
    VideoAdEffectBO doQueryEffect(VideoAdDashboardRequestBO request) {
        VideoAdEffectRequestBO adEffectRequestBO = liveDashboardConverter.convertToVideoAdEffectRequestBO(request);
        VideoAdEffectBO adEffectBO = thirdVideoAdEffectBusiness.getByDaily(adEffectRequestBO);
        return adEffectBO;
    }

    @Override
    public Logger getLog() {
        return log;
    }
}

3、VideoAdStatByJobNumberHandler

具体场景模块的子类

/**
 * 视频广告统计处理器(按职位)
 *
 * @author : 石冬冬-Sieg Heil
 * @since 2022/11/30 9:55 AM
 */
@Service
@Slf4j
public class VideoAdStatByJobNumberHandler extends AbstractVideoAdStatHandler {

    /**
     * 构造函数
     */
    public VideoAdStatByJobNumberHandler() {
        super(StatCaliberEnum.VIDEO_AD_BY_JOB_NUMBER);
    }

    @Override
    VideoAdEffectBO doQueryEffect(VideoAdDashboardRequestBO request) {
        VideoAdEffectRequestBO adEffectRequestBO = liveDashboardConverter.convertToVideoAdEffectRequestBO(request);
        VideoAdEffectBO adEffectBO = thirdVideoAdEffectBusiness.getByDailyWithJobNumber(adEffectRequestBO);
        return adEffectBO;
    }

    @Override
    public Logger getLog() {
        return log;
    }

}

四、总结

1、StatHandleDispatcher 这个类相当于统计处理的路由分发类,它通过公共方法execute(StatContext context) 来对外调用;从源码我们看到,通过@Autowired 自动装配了AbstractStatHandler的所有子类;它起到的另外一个左右,就是外部不需要知道某种场景具体该调用某个处理类,起到一个桥梁的左右。
2、StatContext这个类也是个关键类,具体内部处理逻辑都依赖这个对象,它是一个上下文,所有封装请求参数和处理结果。
3、StatCaliberEnum 这个类,是个统计口径枚举,外部通过查看这个类,就可以知道当前内部统计处理支持哪些场景,它不仅作为StatContext这个类的成员变量,同时也作为AbstractStatHandler这个类的构造函数成员,意味着所有处理类都需要重写抽象类的构造函数,进而指定某个处理类是支持统计场景。
4、整个类通过上下线接,各司其职,最终达到开闭原则。如果修改某个统计处理只需要找到处理类即可;如果新增场景,只需要新增一个处理类来扩展即可。这就是抽象设计的美妙之处。

在这里插入图片描述

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/49031.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

cpu设计和实现(协处理器cp0)

【 声明&#xff1a;版权所有&#xff0c;欢迎转载&#xff0c;请勿用于商业用途。 联系信箱&#xff1a;feixiaoxing 163.com】 除了通用计算器负责控制和计算之外&#xff0c;cpu如果需要正常有序地运行&#xff0c;还需要一定地协处理器来帮助完成对应地工作。在mips下面&am…

Vue实现流程图,借鉴vue-tree-color 实现流程框架技术

Vue实现流程图&#xff0c;借鉴vue-tree-color 实现流程框架技术 文章目录Vue实现流程图&#xff0c;借鉴vue-tree-color 实现流程框架技术借鉴鸣谢演示效果引入依赖添加全局组件的二次封装步骤1 创建组件目录Vuenode.jstree.less使用组件引入使用数据结构案例借鉴鸣谢 实现组…

[附源码]Python计算机毕业设计Django的桌游信息管理系统

项目运行 环境配置&#xff1a; Pychram社区版 python3.7.7 Mysql5.7 HBuilderXlist pipNavicat11Djangonodejs。 项目技术&#xff1a; django python Vue 等等组成&#xff0c;B/S模式 pychram管理等等。 环境需要 1.运行环境&#xff1a;最好是python3.7.7&#xff0c;…

元宇宙产业委调研行杭州站 | 联合西溪谷管委会共商元宇宙赋能实体经济

11月29日下午&#xff0c;由杭州西溪谷建设发展管理委员会和中国移动通信联合会元宇宙产业工作委员会共同举办的“元宇宙赋能实体经济企业家沙龙暨元宇宙产业委调研行杭州站”在西溪谷杭州蚂蚁链产业创新中心召开。20余家元宇宙和区块链企业到场&#xff0c;西溪谷管委会党委书…

pytorch复习笔记--loss.backward()、optimizer.step()和optimizer.zero_grad()的用法

目录 1--loss.backward()的用法 2--optimizer.step()的用法 3--optimizer.zero_grad()的用法 4--举例说明 5--参考 1--loss.backward()的用法 作用&#xff1a;将损失loss向输入测进行反向传播&#xff1b;这一步会计算所有变量x的梯度值 &#xff0c;并将其累积为 进行备…

迎合国家新政策,共享购联合共享经济,三方互利,消费增值

共享单车打通出行“最后一公里”&#xff0c;共享充电宝让人们出门在外免于“电量烦恼”&#xff0c;共享办公降低办公成本……共享经济已深入到人们日常生活。近日&#xff0c;国家信息中心发布的《中国共享经济发展报告&#xff08;2022&#xff09;》显示&#xff0c;2021年…

EMR-Jindo Spark 核心引擎优化

Jindo-Spark 是阿里云智能E-MapReduce 团队在开源的Apache Spark 基础上自主研发的分布式云原生 OLAP 引擎&#xff0c;已经在近千E-MapReduce 客户中大规模部署使用。Jindo Spark 在开源版本基础上做了大量优化和扩展&#xff0c;深度集成和连接了众多阿里云基础服务。凭借该引…

工作流-流程实例【ProcessInstance】与执行实例【Execution】

一、ProcessInstance与Execution的区别 这是一个Activiti的难点&#xff0c;能够懂得这个&#xff0c;工作流也就入门大半了。 下面&#xff0c;我就细致的讲解一下他们的区别。 &#xff08;1&#xff09;首先&#xff0c;我们来看一张我总结的图片&#xff08;这个图片中两条…

Flink-处理函数以及TopN运用案例

7 处理函数 7.1 概述 更底层的操作&#xff0c;直接对流进行操作&#xff0c;直接调用处理函数 7.2 基本处理函数ProcessFunction 分析 ProcessFunction的来源 处理函数继承了AbstractRichFunction富函数抽象类&#xff0c;因此就具有访问状态(state)和其他运行时环境 例…

Day39——Dp专题

文章目录01背包二维数组一维数组6.整数拆分7.不同的二叉搜索01背包 01背包&#xff1a;每一个物品只能选一次&#xff0c;选或者不选 状态标识&#xff1a;f[i][j]&#xff1a;所有只考虑前i个物品&#xff0c;且总体积不超j的所有选法的集合 属性&#xff1a;Max 状态计算&a…

链表之反转链表

文章目录链表之反转链表题目描述解题思路代码实现链表之反转链表 力扣链接 题目描述 定义一个函数&#xff0c;输入一个链表的头节点&#xff0c;反转该链表并输出反转后链表的头节点。 示例&#xff1a; ​ 输入: 1->2->3->4->5->NULL ​ 输出: 5->4-&…

如何设计高性能架构

高性能复杂度模型 高性能复杂度分析和设计 单机 集群 任务分配 将任务分配给多个服务器执行 复杂度分析 增加“任务分配器”节点&#xff0c;可以是独立的服务器&#xff0c;也可以是SDK任务分配器需要管理所有的服务器&#xff0c;可以通过配置文件&#xff0c;也可以通过…

RK3588移植-opencv交叉编译aarch64

文章参考&#xff1a;https://blog.csdn.net/KayChanGEEK/article/details/80365320 文章目录概括准备资源交叉编译OPENCV修改CMakelist文件将lib库复制到/lib目录注意&#xff1a;本文中的所有配置相关路径都与当前安装的路径有关&#xff0c;需要根据自己的环境进行自行修改&…

『Java课设』JavaSwing+MySQL实现学生成绩管理系统

&#x1f468;‍&#x1f393;作者简介&#xff1a;一位喜欢写作&#xff0c;计科专业大三菜鸟 &#x1f3e1;个人主页&#xff1a;starry陆离 如果文章有帮到你的话记得点赞&#x1f44d;收藏&#x1f497;支持一下哦 『Java课设』JavaSwingMySQL实现学生成绩管理系统前言1.开…

SparkMlib 之随机森林及其案例

文章目录什么是随机森林&#xff1f;随机森林的优缺点随机森林示例——鸢尾花分类什么是随机森林&#xff1f; 随机森林算法是机器学习、计算机视觉等领域内应用极为广泛的一个算法&#xff0c;它不仅可以用来做分类&#xff0c;也可用来做回归即预测&#xff0c;随机森林机由…

RabbitMQ之可靠性分析

在使用任何消息中间件的过程中&#xff0c;难免会出现消息丢失等异常情况&#xff0c;这个时候就需要有一个良好的机制来跟踪记录消息的过程&#xff08;轨迹溯源&#xff09;&#xff0c;帮助我们排查问题。在RabbitMQ 中可以使用Firehose 功能来实现消息追踪&#xff0c;Fire…

艾美捷MTT细胞增殖检测试剂盒结果示例引用文献

艾美捷MTT细胞增殖检测试剂盒测定原理&#xff1a; 该试剂盒提供了比色形式测量和监测细胞增殖&#xff0c;含有足够的试剂用于评估在96孔板中进行960次测定或在24孔板中进行192次测定。细胞可以被镀&#xff0c;然后用影响增殖的化合物或药剂。然后用增殖试剂检测细胞&#x…

3.矩阵计算及导数基础

1. 梯度 将导数拓展到向量。 1. 标量对向量求导 x是列向量&#xff0c;y是标量&#xff0c;求导之后变成了行向量 ps&#xff1a; x1^2 2x2^2 这个函数可以画成等高线,对于&#xff08;x1&#xff0c;x2&#xff09;这个点&#xff0c;可以做等高线的切线&#xff0c;再做出…

Spark Streaming(二)

声明&#xff1a; 文章中代码及相关语句为自己根据相应理解编写&#xff0c;文章中出现的相关图片为自己实践中的截图和相关技术对应的图片&#xff0c;若有相关异议&#xff0c;请联系删除。感谢。转载请注明出处&#xff0c;感谢。 By luoyepiaoxue2014 B站&#xff…

动态规划算法(2)最长回文子串详解

文章目录最长回文字串动态规划代码示例前篇&#xff1a; &#xff08;1&#xff09;初识动态规划 最长回文字串 传送门&#xff1a; https://leetcode.cn/problems/longest-palindromic-substring/description/ 给你一个字符串 s&#xff0c;找到 s 中最长的回文子串。 s &qu…