作为一名后端开发,最近在做会员活动相关的用户端系统时,踩了个典型的协同开发坑:管理端和用户端并行开发,管理端负责配置活动规则,用户端需要根据配置展示活动内容,但测试数据源和生产数据源格式不统一、切换数据源时代码耦合度高,改一行代码要牵一发而动全身。折腾了几天,最终用「工厂 + 策略模式搞定多数据源注册 / 切换」+「适配器模式统一数据格式」的组合拳解决了问题,代码扩展性拉满,现在分享给大家。
一、先说说遇到的核心痛点
我们的业务场景是:会员活动管理端有个配置页面,运营可以配置活动规则(比如满减、抽奖),用户端需要读取这些配置并展示给用户。但开发过程中遇到两个核心问题:
- 多数据源切换麻烦:开发阶段用测试数据源(本地 JSON / 测试库),联调 / 上线用生产数据源(正式数据库),之前直接在代码里写 if-else 判断,新增数据源时要改多处代码,耦合度极高;
- 数据格式不统一:测试数据源返回的是扁平化 JSON,生产数据源返回的是嵌套 JSON,字段名也不一致(比如测试里的
activityNamevs 生产里的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 }
四、实际应用效果
- 数据源切换零成本:管理端配置页面可以配置用户端使用的数据源类型(比如测试环境配置 TEST,生产环境配置 PROD),用户端只需传入配置的类型,无需修改任何业务代码;
- 新增数据源易扩展:如果后续要加 “预发布数据源”,只需新增:① 数据源策略类(PreReleaseDataSourceStrategy);② 原始数据 DTO;③ 适配器类(PreReleaseDataAdapter);④ 在工厂里注册,全程不改动原有代码,符合 “开闭原则”;
- 数据格式统一:用户端只需要处理 ActivityVO 这一种格式,无需关心底层数据源的格式差异,前端对接也更省心;
- 协同开发效率提升:管理端配置规则后,用户端不管是连测试库还是生产库,都能拿到统一格式的数据,不用等管理端适配完再开发。
五、踩坑点和优化建议
- 工厂注册的优雅性:上面的工厂用 instanceof 判断类型,可优化为自定义注解(比如 @DataSourceStrategy (type = TEST)),通过反射扫描注解完成注册,更优雅;
- 空值处理:实际开发中要给适配器加空值判断(比如生产数据源的 timeInfo 可能为 null),避免 NPE;
- 数据源配置化:把数据源类型配置到 Nacos/Apollo 等配置中心,支持动态切换,不用重启服务;
- 异常统一处理:给策略类和适配器类加统一的异常捕获,返回友好的错误信息。
六、总结
设计模式不是 “炫技”,而是解决实际问题的工具。这次用工厂 + 策略 + 适配器模式,核心是把 “数据源读取” 和 “数据格式转换” 这两个变化点封装起来,让核心业务逻辑不受底层数据源变化的影响。
最终实现的效果:管理端只管配置活动规则,用户端只管展示统一格式的活动数据,开发过程中不用再为数据源切换和格式适配扯皮,效率提升了不少。
如果你的项目也遇到多数据源、数据格式不统一的问题,不妨试试这个组合拳,亲测好用~