搞定多数据源 + 统一数据格式!用工厂 / 策略 / 适配器模式解决用户端与管理端协同开发痛点

news/2025/11/28 22:39:00/文章来源:https://www.cnblogs.com/itqwb/p/19284338
作为一名后端开发,最近在做会员活动相关的用户端系统时,踩了个典型的协同开发坑:管理端和用户端并行开发,管理端负责配置活动规则,用户端需要根据配置展示活动内容,但测试数据源和生产数据源格式不统一、切换数据源时代码耦合度高,改一行代码要牵一发而动全身。折腾了几天,最终用「工厂 + 策略模式搞定多数据源注册 / 切换」+「适配器模式统一数据格式」的组合拳解决了问题,代码扩展性拉满,现在分享给大家。

一、先说说遇到的核心痛点

我们的业务场景是:会员活动管理端有个配置页面,运营可以配置活动规则(比如满减、抽奖),用户端需要读取这些配置并展示给用户。但开发过程中遇到两个核心问题:
 
  1. 多数据源切换麻烦:开发阶段用测试数据源(本地 JSON / 测试库),联调 / 上线用生产数据源(正式数据库),之前直接在代码里写 if-else 判断,新增数据源时要改多处代码,耦合度极高;
  2. 数据格式不统一:测试数据源返回的是扁平化 JSON,生产数据源返回的是嵌套 JSON,字段名也不一致(比如测试里的activityName vs 生产里的name),用户端要适配多种格式,代码越写越乱。
 
最终决定用设计模式解决:工厂 + 策略模式处理多数据源注册 / 切换,适配器模式统一数据格式,既解耦又易扩展。

二、核心设计思路

先梳理下整体流程,避免大家绕晕:
用户端请求 → 数据源工厂(根据配置)创建对应数据源策略 → 策略读取原始数据 → 适配器工厂创建对应适配器 → 适配器转换为统一格式 → 返回给用户端
各模式的作用: 

策略模式:定义不同数据源的读取逻辑(比如测试数据源读取 JSON,生产数据源读取数据库),封装成独立策略类,避免 if-else;

工厂模式:负责创建策略类 / 适配器类,对外屏蔽创建细节,用户端只需要指定数据源类型即可;

适配器模式:将不同数据源返回的异构数据,转换成用户端统一的 VO 格式,实现 “对内兼容差异,对外统一输出”。

三、代码实现(可直接复用)

先说明技术栈:Java(Spring Boot),核心逻辑可无缝迁移到其他语言(比如 Python/Go)。

第一步:定义基础枚举和实体类

1. 数据源类型枚举

1 /**
2  * 数据源类型枚举
3  */
4 public enum DataSourceType {
5     // 测试数据源(本地JSON)
6     TEST,
7     // 生产数据源(正式数据库)
8     PROD
9 }

2. 原始数据 DTO(不同数据源的原始格式)

 1 /**
 2  * 测试数据源原始数据DTO
 3  */
 4 @Data
 5 public class TestActivityDTO {
 6     // 活动ID
 7     private Long id;
 8     // 活动名称
 9     private String activityName;
10     // 活动开始时间
11     private String startDate;
12     // 活动规则(扁平化)
13     private String rule;
14     // 活动状态(0-未开始,1-进行中)
15     private Integer status;
16 }
17 
18 /**
19  * 生产数据源原始数据DTO
20  */
21 @Data
22 public class ProdActivityDTO {
23     // 活动ID
24     private Long activityId;
25     // 活动名称
26     private String name;
27     // 时间信息(嵌套对象)
28     private TimeInfo timeInfo;
29     // 规则信息(嵌套对象)
30     private RuleInfo ruleInfo;
31     // 活动状态(enable/disable)
32     private String status;
33 
34     @Data
35     public static class TimeInfo {
36         private String startTime;
37     }
38 
39     @Data
40     public static class RuleInfo {
41         private String content;
42     }
43 }

3. 用户端统一 VO(最终对外输出的格式)

 1 /**
 2  * 用户端统一活动VO(对外输出)
 3  */
 4 @Data
 5 public class ActivityVO {
 6     // 活动ID
 7     private Long activityId;
 8     // 活动名称
 9     private String activityName;
10     // 活动开始时间
11     private LocalDateTime startTime;
12     // 活动规则
13     private String activityRule;
14     // 活动状态(0-未开始,1-进行中)
15     private Integer activityStatus;
16 }

第二步:多数据源策略 + 工厂实现

1. 数据源策略接口

1 /**
2  * 数据源策略接口(定义读取数据的统一方法)
3  */
4 public interface DataSourceStrategy {
5     /**
6      * 根据活动ID读取原始数据
7      */
8     Object getActivityData(Long activityId);
9 }

2. 具体数据源策略实现

 1 /**
 2  * 测试数据源策略(读取本地JSON)
 3  */
 4 @Component
 5 public class TestDataSourceStrategy implements DataSourceStrategy {
 6 
 7     @Override
 8     public Object getActivityData(Long activityId) {
 9         // 模拟读取本地JSON文件(实际开发可替换为真实读取逻辑)
10         TestActivityDTO dto = new TestActivityDTO();
11         dto.setId(activityId);
12         dto.setActivityName("测试满减活动");
13         dto.setStartDate("2025-11-28 10:00:00");
14         dto.setRule("满100减20,仅限会员");
15         dto.setStatus(1);
16         return dto;
17     }
18 }
19 
20 /**
21  * 生产数据源策略(读取数据库)
22  */
23 @Component
24 public class ProdDataSourceStrategy implements DataSourceStrategy {
25 
26     @Override
27     public Object getActivityData(Long activityId) {
28         // 模拟从数据库读取数据(实际开发可替换为MyBatis/Repository查询)
29         ProdActivityDTO dto = new ProdActivityDTO();
30         dto.setActivityId(activityId);
31         dto.setName("正式满减活动");
32         
33         ProdActivityDTO.TimeInfo timeInfo = new ProdActivityDTO.TimeInfo();
34         timeInfo.setStartTime("2025-11-28 10:00:00");
35         dto.setTimeInfo(timeInfo);
36         
37         ProdActivityDTO.RuleInfo ruleInfo = new ProdActivityDTO.RuleInfo();
38         ruleInfo.setContent("满100减20,仅限会员");
39         dto.setRuleInfo(ruleInfo);
40         
41         dto.setStatus("enable");
42         return dto;
43     }
44 }

3. 数据源工厂(核心:注册 + 创建策略)

 1 /**
 2  * 数据源工厂(负责注册和创建数据源策略)
 3  */
 4 @Component
 5 public class DataSourceFactory {
 6 
 7     // 缓存所有数据源策略(key:数据源类型,value:策略实例)
 8     private final Map<DataSourceType, DataSourceStrategy> strategyMap = new HashMap<>();
 9 
10     // 利用Spring自动注入所有DataSourceStrategy实现类,完成注册
11     public DataSourceFactory(List<DataSourceStrategy> strategyList) {
12         for (DataSourceStrategy strategy : strategyList) {
13             if (strategy instanceof TestDataSourceStrategy) {
14                 strategyMap.put(DataSourceType.TEST, strategy);
15             } else if (strategy instanceof ProdDataSourceStrategy) {
16                 strategyMap.put(DataSourceType.PROD, strategy);
17             }
18             // 新增数据源时,只需添加新的case,无需修改其他代码
19         }
20     }
21 
22     /**
23      * 根据数据源类型获取策略实例
24      */
25     public DataSourceStrategy getStrategy(DataSourceType type) {
26         DataSourceStrategy strategy = strategyMap.get(type);
27         if (strategy == null) {
28             throw new IllegalArgumentException("不支持的数据源类型:" + type);
29         }
30         return strategy;
31     }
32 }

第三步:数据适配器 + 工厂实现

1. 适配器接口

1 /**
2  * 数据适配器接口(定义数据转换的统一方法)
3  */
4 public interface DataAdapter {
5     /**
6      * 将原始数据转换为统一VO
7      */
8     ActivityVO adapt(Object rawData);
9 }

2. 具体适配器实现

 1 /**
 2  * 测试数据源适配器(TestActivityDTO → ActivityVO)
 3  */
 4 @Component
 5 public class TestDataAdapter implements DataAdapter {
 6 
 7     @Override
 8     public ActivityVO adapt(Object rawData) {
 9         if (!(rawData instanceof TestActivityDTO)) {
10             throw new IllegalArgumentException("原始数据类型不匹配,期望TestActivityDTO");
11         }
12         TestActivityDTO dto = (TestActivityDTO) rawData;
13         ActivityVO vo = new ActivityVO();
14         vo.setActivityId(dto.getId());
15         vo.setActivityName(dto.getActivityName());
16         // 时间格式转换
17         vo.setStartTime(LocalDateTime.parse(dto.getStartDate(), DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss")));
18         vo.setActivityRule(dto.getRule());
19         vo.setActivityStatus(dto.getStatus());
20         return vo;
21     }
22 }
23 
24 /**
25  * 生产数据源适配器(ProdActivityDTO → ActivityVO)
26  */
27 @Component
28 public class ProdDataAdapter implements DataAdapter {
29 
30     @Override
31     public ActivityVO adapt(Object rawData) {
32         if (!(rawData instanceof ProdActivityDTO)) {
33             throw new IllegalArgumentException("原始数据类型不匹配,期望ProdActivityDTO");
34         }
35         ProdActivityDTO dto = (ProdActivityDTO) rawData;
36         ActivityVO vo = new ActivityVO();
37         vo.setActivityId(dto.getActivityId());
38         vo.setActivityName(dto.getName());
39         // 嵌套字段提取+时间转换
40         vo.setStartTime(LocalDateTime.parse(dto.getTimeInfo().getStartTime(), DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss")));
41         vo.setActivityRule(dto.getRuleInfo().getContent());
42         // 状态映射(生产的enable→1,disable→0)
43         vo.setActivityStatus("enable".equals(dto.getStatus()) ? 1 : 0);
44         return vo;
45     }
46 }

3. 适配器工厂

 1 /**
 2  * 适配器工厂(根据数据源类型创建对应适配器)
 3  */
 4 @Component
 5 public class DataAdapterFactory {
 6 
 7     private final Map<DataSourceType, DataAdapter> adapterMap = new HashMap<>();
 8 
 9     // Spring自动注入所有DataAdapter实现类,完成注册
10     public DataAdapterFactory(List<DataAdapter> adapterList) {
11         for (DataAdapter adapter : adapterList) {
12             if (adapter instanceof TestDataAdapter) {
13                 adapterMap.put(DataSourceType.TEST, adapter);
14             } else if (adapter instanceof ProdDataAdapter) {
15                 adapterMap.put(DataSourceType.PROD, adapter);
16             }
17             // 新增数据源时,添加新的适配器注册即可
18         }
19     }
20 
21     /**
22      * 根据数据源类型获取适配器
23      */
24     public DataAdapter getAdapter(DataSourceType type) {
25         DataAdapter adapter = adapterMap.get(type);
26         if (adapter == null) {
27             throw new IllegalArgumentException("不支持的数据源类型:" + type);
28         }
29         return adapter;
30     }
31 }

第四步:核心业务服务(整合所有逻辑)

 1 /**
 2  * 活动数据核心服务(对外提供统一接口)
 3  */
 4 @Service
 5 public class ActivityDataService {
 6 
 7     @Resource
 8     private DataSourceFactory dataSourceFactory;
 9 
10     @Resource
11     private DataAdapterFactory dataAdapterFactory;
12 
13     /**
14      * 获取统一格式的活动数据
15      * @param dataSourceType 数据源类型
16      * @param activityId 活动ID
17      * @return 统一VO
18      */
19     public ActivityVO getActivityVO(DataSourceType dataSourceType, Long activityId) {
20         // 1. 获取数据源策略,读取原始数据
21         DataSourceStrategy strategy = dataSourceFactory.getStrategy(dataSourceType);
22         Object rawData = strategy.getActivityData(activityId);
23         
24         // 2. 获取适配器,转换为统一VO
25         DataAdapter adapter = dataAdapterFactory.getAdapter(dataSourceType);
26         return adapter.adapt(rawData);
27     }
28 }

第五步:测试验证

 1 /**
 2  * 测试入口(模拟用户端调用)
 3  */
 4 @SpringBootTest
 5 public class ActivityDataTest {
 6 
 7     @Resource
 8     private ActivityDataService activityDataService;
 9 
10     @Test
11     public void testTestDataSource() {
12         // 测试数据源
13         ActivityVO vo = activityDataService.getActivityVO(DataSourceType.TEST, 1L);
14         System.out.println("测试数据源转换结果:" + vo);
15         // 输出:ActivityVO(activityId=1, activityName=测试满减活动, startTime=2025-11-28T10:00, activityRule=满100减20,仅限会员, activityStatus=1)
16     }
17 
18     @Test
19     public void testProdDataSource() {
20         // 生产数据源
21         ActivityVO vo = activityDataService.getActivityVO(DataSourceType.PROD, 1L);
22         System.out.println("生产数据源转换结果:" + vo);
23         // 输出:ActivityVO(activityId=1, activityName=正式满减活动, startTime=2025-11-28T10:00, activityRule=满100减20,仅限会员, activityStatus=1)
24     }
25 }

四、实际应用效果

  1. 数据源切换零成本:管理端配置页面可以配置用户端使用的数据源类型(比如测试环境配置 TEST,生产环境配置 PROD),用户端只需传入配置的类型,无需修改任何业务代码;
  2. 新增数据源易扩展:如果后续要加 “预发布数据源”,只需新增:① 数据源策略类(PreReleaseDataSourceStrategy);② 原始数据 DTO;③ 适配器类(PreReleaseDataAdapter);④ 在工厂里注册,全程不改动原有代码,符合 “开闭原则”;
  3. 数据格式统一:用户端只需要处理 ActivityVO 这一种格式,无需关心底层数据源的格式差异,前端对接也更省心;
  4. 协同开发效率提升:管理端配置规则后,用户端不管是连测试库还是生产库,都能拿到统一格式的数据,不用等管理端适配完再开发。

五、踩坑点和优化建议

  1. 工厂注册的优雅性:上面的工厂用 instanceof 判断类型,可优化为自定义注解(比如 @DataSourceStrategy (type = TEST)),通过反射扫描注解完成注册,更优雅;
  2. 空值处理:实际开发中要给适配器加空值判断(比如生产数据源的 timeInfo 可能为 null),避免 NPE;
  3. 数据源配置化:把数据源类型配置到 Nacos/Apollo 等配置中心,支持动态切换,不用重启服务;
  4. 异常统一处理:给策略类和适配器类加统一的异常捕获,返回友好的错误信息。

六、总结

设计模式不是 “炫技”,而是解决实际问题的工具。这次用工厂 + 策略 + 适配器模式,核心是把 “数据源读取” 和 “数据格式转换” 这两个变化点封装起来,让核心业务逻辑不受底层数据源变化的影响。
 
最终实现的效果:管理端只管配置活动规则,用户端只管展示统一格式的活动数据,开发过程中不用再为数据源切换和格式适配扯皮,效率提升了不少。
 
如果你的项目也遇到多数据源、数据格式不统一的问题,不妨试试这个组合拳,亲测好用~

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

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

相关文章

测试档案

测试1测试文章测试标头测试代码

GPU内存层次结构如何影响计算体验

本文深入探讨GPU内存层次结构对计算性能的影响,涵盖CUDA内存类型、缓存层级以及H100系列的新特性,帮助开发者优化内存访问延迟、最大化内存带宽并降低功耗,实现GPU性能的极致发挥。GPU内存层次结构:隐藏的性能瓶颈…

P13270 【模板】最小表示法

题目背景 原模板题:P1368 工艺。 题目描述 若长度为 \(n\) 的字符串 \(s\) 中可以选择一个位置 \(i\),使得 \(\overline{s_i\cdots s_ns_1\cdots s_{i-1}}=t\),则称 \(s\) 与 \(t\) 循环同构。字符串 \(s\) 的最小表…

P5357 【模板】AC 自动机

题目背景 本题原为“AC 自动机(二次加强版)”。完成本题前可以先完成 AC 自动机(简单版) 和 AC 自动机(简单版 II) 两道题,为 AC 自动机更简单的应用。 题目描述 给你一个文本串 \(S\) 和 \(n\) 个模式串 \(T_{…

分布式Session会话实现优秀的方案

pre { white-space: pre !important; word-wrap: normal !important; overflow-x: auto !important; display: block !important; font-family: "Consolas", "Monaco", "Courier New", …

Revive Adserver存储型XSS漏洞技术分析

本文详细分析了Revive Adserver广告管理系统中的存储型XSS安全漏洞,涉及inventory-retrieve.php和campaign-edit.php文件,包含漏洞复现步骤、技术原理和修复方案,已分配CVE-2025-52667编号。Revive Adserver存储型X…

2025年终总结

多好的35岁,人生黄金期,全盛时期。 2024年年终总结写道:“2024年是个好年份。今年过得真好,有滋有味。果真人生至味是清欢。2024年第一次享受平静,第一次没有大的目标、计划、挑战。只是静心生活,学习,做事。20…

局域网---局域网传输文件及共享桌面

我想要在局域网内进行文件传输以及共享桌面,可以使用文件快传:https://transfer.52python.cn/软件界面:该软件可以联网使用,可以本地部署使用:打完收工!

P2709 【模板】莫队 / 小B的询问

题目描述 小 B 有一个长为 \(n\) 的整数序列 \(a\),值域为 \([1,k]\)。 他一共有 \(m\) 个询问,每个询问给定一个区间 \([l,r]\),求: \[\sum\limits_{i=1}^k c_i^2 \]其中 \(c_i\) 表示数字 \(i\) 在 \([l,r]\) 中…

并不打算的

光和热发货的干扰星级酒店工业

P1903 【模板】带修莫队 / [国家集训队] 数颜色 / 维护队列

题目描述 墨墨购买了一套 \(N\) 支彩色画笔(其中有些颜色可能相同),摆成一排,你需要回答墨墨的提问。墨墨会向你发布如下指令:\(Q\ L\ R\) 代表询问你从第 \(L\) 支画笔到第 \(R\) 支画笔中共有几种不同颜色的画笔…

P1883 【模板】三分 / 函数

题目描述 给定 \(n\) 个二次函数 \(f_1(x),f_2(x),\dots,f_n(x)\)(均形如 \(ax^2+bx+c\)),设 \(F(x)=\max\{f_1(x),f_2(x),...,f_n(x)\}\),求 \(F(x)\) 在区间 \([0,1000]\) 上的最小值。 输入格式 输入第一行为正…

CSP2025 T4

Sol 赛时是不是多想想就会了??? 考虑 \(f_{i,j,k}\) 表示前 \(i\) 个位置,干掉了 \(j\) 个人,然后有 \(k\) 个位置已经被钦定了。 如果 \(s_i=1\),令 \(c_i\) 表示忍耐度 \(\le i\) 的人数,那么当前可以选的人数…

Day5 Scrum冲刺博客

Day5 Scrum冲刺博客 1. 团队会议 todo补充会议照片 1)昨天已完成的工作前端初步完成了四个通知栏目页的搭建后端完成了数据库建立的完整流程 完成了时间信息的提取函数 规定了部分接口格式测试检查新加入各代码文件格…

台达变频器与西门子1200 PLC互联借Modbus RTU转Profinet推动工业物联网

一、案例项目背景 在工业自动化与工业物联网深度融合的趋势下,新能源电池行业作为战略性新兴产业,正朝着高效化、智能化方向快速发展。某头部新能源电池生产企业新建一条方形动力电池模组装配线,核心控制单元采用西…

2025-11-28

CF Problem - 1766C - Codeforces(1300)(dp)(模拟) 一笔画,要经过所有黑色,并且有且仅有一次 不能经过白色 #include <bits/stdc++.h> using namespace std; #define LL long long const LL mod = 9982443…

Convolutional Neutral Network(CNN网络)

CNN(卷积神经网络) 笔记出处:李宏毅讲CNN 根据下面几个问题理清楚CNN的结构原理 1. CNN的哪些特点导致适合做类似图像分类这种图像任务?图像分类这类型的任务包括: <1> 不需要图片中所有的信息,可能只需要…

二维偏序(离线二维数点)

二维偏序(离线二维数点) 问题 在 \([l,r]\) 的区间内,有多少个数 \(\le x\)。共 \(m\) 次询问。 暴力:\(O(nm)\) 的 check。效率低下。 离线二维数点 可以将询问离线下来。 首先运用下差分的思想,将 \(ans[l,r]\)…

Product Hunt 每日热榜 | 2025-10-30 - 教程

pre { white-space: pre !important; word-wrap: normal !important; overflow-x: auto !important; display: block !important; font-family: "Consolas", "Monaco", "Courier New", …

2025年Q4球墨铸铁管厂家TOP5排行榜:场景适配+成本优化,采购选型指南

一、行业现状与榜单评估体系 据 2025 年行业权威数据统计,国内球墨铸铁管年需求量已突破 280 万吨,市场规模超 300 亿元,其中市政供水、水利工程及工业排污三大核心领域需求占比高达 78%,华东、华南、西南三大区域…