应定义具体输入输出边界的泛型接口(如IChargeProcessor),避免空接口和业务名词命名,用DI容器管理策略集合并配合元数据或配置驱动路由。

Strategy 接口怎么定义才不踩类型擦除或运行时转型的坑
策略模式在 C# 里不是靠“接口+一堆 if”硬凑出来的,核心是让 IStrategy 的契约足够窄、足够具体,否则你很快会遇到“这个策略要传 ID,那个要传 DTO,第三个还要异步”,最后被迫用 object 或泛型堆砌,反而更难维护。
推荐做法:按**输入输出边界**拆策略,而不是按业务名词。比如不是 IPaymentStrategy,而是 IChargeProcessor —— 明确谁调用、给什么、返回什么。
- 避免定义空接口
IStrategy,它无法约束行为,后续只能靠注释或约定,一加新策略就崩 - 不要把“日志”“重试”“缓存”等横切逻辑塞进策略实现里,它们该由装饰器或中间件处理
- 如果策略间共享大量字段(比如都用到
HttpClient或IConfiguration),用构造函数注入,别塞进执行方法参数里
.NET 6+ 怎么注册和解析策略集合(不用手写 Dictionary)
手动维护 Dictionary 看似简单,但一升级到多租户、动态加载插件或需要生命周期管理(如 Scoped 策略),立刻出问题:服务没注册、重复注册、作用域错乱、Key 写错没提示。
正确姿势是交给 DI 容器管策略集合,靠 AddTransient + GetServices,再配合一个轻量路由层做分发。
- 注册时用命名泛型或元数据标记(比如
[StrategyName("alipay")]),比字符串 Key 更安全 - 解析时别直接
provider.GetService,要用() provider.GetServices拿全部,再按需筛选() - 如果策略需要依赖
IServiceScope(比如访问 Scoped 服务),确保调用方自己创建 scope,别在策略内部 new ServiceScope
策略执行时报 InvalidOperationException: No service for type 'X' has been registered 怎么快速定位
这个错误不是策略本身写的有问题,而是 DI 配置断在了某一层:要么策略类没注册,要么它依赖的某个服务没注册,要么注册了但生命周期不匹配(比如策略是 Transient,却注入了一个 Singleton 服务,而该服务又依赖 Scoped 服务)。
最有效的排查路径是反向查依赖树:从报错策略类开始,逐个检查它的构造函数参数是否都在 Program.cs 或 Startup.cs 里注册过,尤其注意 IOptions、IConfiguration 这类“看起来自带”的服务,其实也要显式 AddOptions()。
- 用
dotnet trace或第三方库(如 Scrutor)打印所有已注册服务,确认目标策略类型在列表中 - 如果策略类有多个构造函数,DI 默认选参数最多的那个 —— 如果你写了两个构造函数,其中一个缺参数,它可能误选那个
- 别在策略实现里直接 new 对象(尤其是 new HttpClient),这绕过了 DI,也切断了生命周期管理
策略太多时,如何避免每次新增都要改路由逻辑(比如 switch 或 Dictionary.Add)
硬编码路由(switch (type) { case "wx": return new WxStrategy(); })是策略模式退化成 if-else 的典型信号。真正的解耦在于让策略“自注册”和“自识别”。
可行方案是让每个策略实现一个静态 Supports(string key) 方法,或者实现 IGetsHandledBy 接口,然后用反射扫描程序集自动收集;更稳妥的是结合配置驱动:把策略名和类型映射写进 appsettings.json,启动时读取并注册。
- 反射扫描适合内部系统,但要注意 .NET AOT 或 Trim 时类型被剪掉,得加
DynamicDependency特性 - 配置驱动更稳定,但要求策略类型必须有无参构造函数或支持工厂模式
- 无论哪种方式,都要加单元测试验证:新增策略后,
GetServices是否增加,且能按预期 key 解析出来().Count()
策略模式真正难的不是写几个类,而是划清“策略边界”和“上下文职责”。很多人卡在“这个判断该放策略里,还是该由上下文决定”,结果策略越来越胖,上下文越来越薄。盯住一点:策略只负责“怎么做”,不负责“该不该做”和“做了之后怎么收尾”。










