news 2026/6/20 18:32:30

工厂模式与策略模式的深度实践:从代码优化到架构思考

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
工厂模式与策略模式的深度实践:从代码优化到架构思考

作者:打码养家
日期:2025年12月19日
场景:第三方登录系统重构(钉钉、企业微信等)

一、背景:为什么需要重构?

在开发一个 SaaS 平台时,我们最初采用最朴素的方式实现第三方登录:

// ❌ 初始版本:硬编码 + if-else 地狱 @PostMapping("/login") public LoginInfoVo thirdPartyLogin(@RequestParam String provider, @RequestParam String code) { if ("dingtalk".equals(provider)) { // 钉钉登录逻辑(50行) return dingTalkService.login(code); } else if ("wecom".equals(provider)) { // 企业微信登录逻辑(60行) return weComService.login(code); } else if ("feishu".equals(provider)) { // 飞书登录逻辑(55行) return feishuService.login(code); } throw new IllegalArgumentException("不支持的登录方式: " + provider); }

🚨 问题暴露:

  1. 违反开闭原则:每新增一个登录方式,都要修改 Controller。
  2. 职责混乱:Controller 承担了“选择逻辑”和“业务调用”双重职责。
  3. 难以测试:无法单独测试某一种登录策略。
  4. 耦合严重:Controller 直接依赖所有 Service。

二、第一次尝试:仅使用策略模式

我首先想到用策略模式解耦:

// ✅ 策略接口 public interface ThirdPartyLoginStrategy { LoginInfoVo login(String code); String getProvider(); } // ✅ 具体策略 @Component public class DingTalkLoginStrategy implements ThirdPartyLoginStrategy { private final DingTalkService dingTalkService; // Spring Bean public DingTalkLoginStrategy(DingTalkService service) { this.dingTalkService = service; } @Override public LoginInfoVo login(String code) { return dingTalkService.login(code); } @Override public String getProvider() { return "dingtalk"; } }

❓ 我的第一个疑问:客户端怎么用?

如果不用工厂,客户端只能这样写:

// ❌ 客户端仍需硬编码 if ("dingtalk".equals(provider)) { strategy = new DingTalkLoginStrategy(dingTalkService); // ← 但 dingTalkService 是 Spring Bean! }

💥问题来了new DingTalkLoginStrategy(...)会导致dingTalkService为 null!
因为 Spring 无法管理手动new出来的对象。

结论:纯策略模式在 Spring 环境中无法直接使用,除非策略不依赖任何 Bean。

例子:

如果只用策略模式(不用工厂),也是完全可行的,而且在某些简单场景下非常合适。但它的适用范围和优缺点与“策略+工厂”组合有明显区别。


✅ (一)、什么是“纯策略模式”?

策略模式的核心思想是:

将一组算法/行为封装成独立的类,它们实现同一个接口,客户端可以在运行时选择使用哪一个。

关键点:客户端自己决定用哪个策略,而不是通过工厂动态获取。


🌰 (二)、纯策略模式示例

1. 策略接口
public interface DiscountStrategy { BigDecimal applyDiscount(BigDecimal amount); }
2. 具体策略
public class VIPDiscountStrategy implements DiscountStrategy { public BigDecimal applyDiscount(BigDecimal amount) { return amount.multiply(BigDecimal.valueOf(0.8)); // 打8折 } } public class SeasonalDiscountStrategy implements DiscountStrategy { public BigDecimal applyDiscount(BigDecimal amount) { return amount.multiply(BigDecimal.valueOf(0.9)); // 打9折 } }
3. 客户端直接使用(无工厂)
public class OrderService { public BigDecimal calculatePrice(String userType, BigDecimal originalPrice) { DiscountStrategy strategy; if ("VIP".equals(userType)) { strategy = new VIPDiscountStrategy(); // ← 客户端自己 new } else { strategy = new SeasonalDiscountStrategy(); // ← 客户端自己判断 } return strategy.applyDiscount(originalPrice); } }

✅ 这就是纯策略模式:没有工厂,客户端直接创建并使用策略。


✅ (三)、什么时候适合只用策略模式?

场景说明
策略数量少(1~3个)比如只有“普通用户”和“VIP”两种折扣
选择逻辑简单固定if-elseswitch足够清晰
策略不需要 Spring Bean不依赖数据库、Redis、其他 Service
性能敏感(避免 Map 查找)直接 new,无额外开销
一次性或脚本式代码不需要长期维护和扩展

💡 例如:工具类中的格式化策略、简单的配置开关等。

三、引入工厂模式:真正的解耦

于是,我决定引入工厂模式,让 Spring 来管理策略的创建和注入。

✅ 最终方案:策略 + 工厂 + Spring 自动注册

1. 策略接口(不变)
public interface ThirdPartyLoginStrategy { LoginInfoVo login(String code); String getProvider(); // 返回唯一标识,如 "dingtalk" }
2. 具体策略(由 Spring 管理)
@Component public class DingTalkLoginStrategy implements ThirdPartyLoginStrategy { private final DingTalkService dingTalkService; public DingTalkLoginStrategy(DingTalkService service) { this.dingTalkService = service; } @Override public LoginInfoVo login(String code) { return dingTalkService.login(code); } @Override public String getProvider() { return "dingtalk"; } }

✅ 所有策略类都加@Component,成为 Spring Bean。

3. 工厂类(核心)
@Component public class LoginStrategyFactory { private final Map<String, ThirdPartyLoginStrategy> strategyMap = new ConcurrentHashMap<>(); // Spring 自动注入所有实现了 ThirdPartyLoginStrategy 的 Bean public LoginStrategyFactory(ThirdPartyLoginStrategy[] strategies) { for (ThirdPartyLoginStrategy strategy : strategies) { strategyMap.put(strategy.getProvider(), strategy); } } public ThirdPartyLoginStrategy getStrategy(String provider) { ThirdPartyLoginStrategy strategy = strategyMap.get(provider); if (strategy == null) { throw new IllegalArgumentException("不支持的登录方式: " + provider); } return strategy; } }
4. 客户端(Controller)
@RestController @RequiredArgsConstructor public class ThirdPartyLoginController { private final LoginStrategyFactory strategyFactory; @PostMapping("/login") public LoginInfoVo login(@RequestParam String provider, @RequestParam String code) { ThirdPartyLoginStrategy strategy = strategyFactory.getStrategy(provider); return strategy.login(code); } }

四、关键设计细节解析

🔍 1. 为什么用构造器注入ThirdPartyLoginStrategy[]

Spring 有一个强大特性:

当注入一个接口数组时,会自动收集容器中所有该接口的实现类 Bean。

private final ThirdPartyLoginStrategy[] strategies;

→ 启动时,Spring 自动把DingTalkLoginStrategyWeComLoginStrategy等全部注入进来。

无需手动注册,无需修改工厂代码!


🔍 2. 为什么用ConcurrentHashMap

  • Controller 可能被高并发调用
  • strategyMap在初始化后只读,但初始化过程需线程安全
  • ConcurrentHashMap保证init()方法在多线程下安全

💡 实际上,由于@PostConstruct只在 Bean 初始化时调用一次,普通HashMap也够用。但用ConcurrentHashMap更严谨。


🔍 3. 为什么策略类必须是@Component

  • 只有被 Spring 管理,才能自动注入DingTalkService等依赖
  • 只有是 Spring Bean,才能被ThirdPartyLoginStrategy[]自动收集

⚠️ 如果忘记加@Component,启动时strategies数组为空!


五、我的深度疑问与解答

❓ 疑问 1:这算“简单工厂”还是“工厂方法”?

  • 这是“简单工厂”(Simple Factory),因为:
    • 一个工厂类(LoginStrategyFactory
    • 通过参数(provider)返回不同产品
  • 不是“工厂方法”(Factory Method),后者需要子类重写创建方法。

✅ 在大多数业务场景中,“简单工厂 + 策略”已足够。


❓ 疑问 2:能否不用工厂,直接用ApplicationContext获取?

可以,但不推荐:

// 不推荐! ThirdPartyLoginStrategy strategy = applicationContext.getBean(provider + "LoginStrategy", ThirdPartyLoginStrategy.class);

问题

  • 需要约定 Bean 名称(脆弱)
  • 无法统一校验“是否支持”
  • 引入全局状态,难以测试

✅ 工厂提供了抽象层,隐藏了获取细节。


❓ 疑问 3:如果策略需要动态配置(如 API 密钥),怎么办?

可以在策略中注入配置:

@Component public class DingTalkLoginStrategy implements ThirdPartyLoginStrategy { private final String appId; private final String appSecret; public DingTalkLoginStrategy(@Value("${dingtalk.app-id}") String appId, @Value("${dingtalk.app-secret}") String appSecret) { this.appId = appId; this.appSecret = appSecret; } }

或者通过配置中心动态加载——不影响工厂+策略结构


❓ 疑问 4:如何支持“默认策略”或“组合策略”?

  • 默认策略:在getStrategy中加 fallback
    if (strategy == null) return defaultStrategy;
  • 组合策略(如先钉钉再微信):新增一个CompositeLoginStrategy实现

✅ 策略模式天然支持扩展。

六、优势总结:为什么这个组合如此强大?

维度优化前优化后
扩展性新增登录方式需改 Controller只需新增一个@Component策略类
可测试性无法单独测试钉钉逻辑可直接new DingTalkLoginStrategy(mock)
可维护性登录逻辑散落在 Controller每个策略独立、职责单一
健壮性魔法字符串"dingtalk"通过getProvider()统一管理
Spring 集成手动调用 Service完全依赖注入,符合 Spring 哲学

七、适用场景扩展

这套模式不仅适用于登录,还可用于:

场景策略标识策略行为
支付网关"alipay","wechat"pay(order)
消息推送"sms","email","wechat"send(message)
文件解析"excel","csv","json"parse(file)
权限校验"role","acl","rbac"check(user, resource)

💡凡是“根据类型执行不同算法”的地方,都适用此模式。

八、避坑指南:常见错误

❌ 错误 1:策略类忘记加@Component

→ 启动时不报错,但运行时找不到策略。

✅ 解决:确保所有策略类被 Spring 扫描到。

❌ 错误 2:getProvider()返回值重复

→ 后注册的策略会覆盖先注册的。

✅ 解决:使用枚举或常量,避免手写字符串。

❌ 错误 3:在策略中做太多事

→ 策略应只负责“协调”,具体逻辑下沉到 Service。

✅ 解决:策略类保持轻量,只调用其他 Service。

九、未来演进方向

  1. 策略元数据化
    用注解定义策略标识:

    @StrategyProvider("dingtalk") public class DingTalkLoginStrategy { ... }
  2. 动态注册/卸载策略
    通过管理后台启用/禁用某些登录方式。

  3. 策略性能监控
    在工厂中加入 Metrics 统计各策略调用次数、耗时。

十、结语

从最初的if-else到现在的策略 + 工厂 + Spring 自动装配,我深刻体会到:

好的设计不是一蹴而就的,而是在解决实际问题中逐步演进的。

这套模式不仅解决了当前需求,更为未来扩展铺平了道路。它体现了面向对象的核心思想:封装变化、依赖抽象、开闭原则

如果你也在处理类似的多实现场景,不妨试试这个组合——它可能比你想象的更强大。

附:完整代码结构

src/ └── main/ └── java/ └── com/rihuayun/auth/ ├── strategy/ │ ├── ThirdPartyLoginStrategy.java // 策略接口 │ ├── DingTalkLoginStrategy.java // 具体策略 │ └── WeComLoginStrategy.java ├── factory/ │ └── LoginStrategyFactory.java // 工厂类 └── controller/ └── ThirdPartyLoginController.java // 客户端
版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/6/21 0:14:52

67、系统配置维护指南

系统配置维护指南 在日常的计算机使用中,定期审查和维护系统的核心配置是非常重要的。通过系统属性对话框,我们可以管理计算机的网络标识、环境变量、用户配置文件等诸多核心配置属性。系统属性对话框包含五个选项卡: - 计算机名 - 硬件 - 高级 - 系统保护 - 远程 下…

作者头像 李华
网站建设 2026/6/20 1:57:38

Langchain-Chatchat支持知识库操作灰度灰度部署吗?

Langchain-Chatchat支持知识库操作灰度部署吗&#xff1f; 在企业级 AI 应用日益深入的今天&#xff0c;一个看似简单的问题背后往往牵动着整套系统的稳定性与演进能力——当我们要更新一份员工手册、发布一项新政策时&#xff0c;如何确保这些变更不会瞬间引发全公司范围内的误…

作者头像 李华
网站建设 2026/6/19 16:17:33

史上最细,CRM管理系统项目(ERP平台)测试与面试汇总(详细)

目录&#xff1a;导读 前言一、Python编程入门到精通二、接口自动化项目实战三、Web自动化项目实战四、App自动化项目实战五、一线大厂简历六、测试开发DevOps体系七、常用自动化测试工具八、JMeter性能测试九、总结&#xff08;尾部小惊喜&#xff09; 前言 CRM客户关系管理系…

作者头像 李华
网站建设 2026/6/20 17:26:21

34、保障家庭电脑使用安全与防范恶意软件

保障家庭电脑使用安全与防范恶意软件 家庭安全管理功能 家庭安全管理功能为家长提供了全面监控和管理孩子电脑使用的有效手段,以下是其主要功能介绍: 1. 活动报告 - 功能概述 :活动报告能够详细记录用户访问的每个网站、进行的每次互联网搜索,以及使用电脑和不同应…

作者头像 李华
网站建设 2026/6/15 21:46:21

47、电脑使用与维护全攻略

电脑使用与维护全攻略 手动连接网络打印机 在使用Windows系统时,如果系统未显示网络打印机的名称,你可以手动进行连接。具体步骤如下: 1. 查找打印机的IP地址 :若打印机有LCD显示屏,通常会有菜单选项来显示当前IP地址;若没有,则可通过特定命令打印网络设置,详细操…

作者头像 李华
网站建设 2026/6/20 14:13:37

单片机毕业设计创新的任务书指导

【单片机毕业设计项目分享系列】 &#x1f525; 这里是DD学长&#xff0c;单片机毕业设计及享100例系列的第一篇&#xff0c;目的是分享高质量的毕设作品给大家。 &#x1f525; 这两年开始毕业设计和毕业答辩的要求和难度不断提升&#xff0c;传统的单片机项目缺少创新和亮点…

作者头像 李华