策略接口应只定义行为契约,不包含状态字段或生命周期方法;策略注册须用serviceprovider而非手动dictionary;context类仅负责分发,选择逻辑应抽离为独立选择器。

策略接口定义要聚焦行为契约,别塞状态字段
策略模式的核心是把算法的「变化点」抽象成接口,而不是提前规划所有可能的字段。比如你要封装不同排序逻辑,ISortStrategy 只该有 Sort(int[] data) 方法,别加 IsStable 或 MaxDepth 这类配置属性——这些属于具体策略内部实现细节,或应通过构造函数注入。
常见错误是把策略接口设计得像配置类:ISortStrategy 里塞一堆 get 属性,结果每个实现都返回默认值,徒增维护成本。
- 接口方法参数尽量精简,必要时用
record封装输入(如SortOptions),但别让接口本身承担配置职责 - 避免在接口中定义
Init()、Dispose()等生命周期方法——策略实例应是无状态、可复用的 - 如果算法需要外部服务(如日志、缓存),通过构造函数传入依赖,而非在接口里暴露
SetLogger(ILogger)这类 setter
策略注册与解析要用 ServiceProvider,别手写 Dictionary + new
手动维护 Dictionary<string isortstrategy></string> 并用 new QuickSortStrategy() 填充,会带来硬编码、生命周期失控、测试困难三重问题。.NET 6+ 应直接用 IServiceCollection 注册策略族,再通过 IServiceProvider 解析。
例如注册多个排序策略:
services.AddSingleton<ISortStrategy, QuickSortStrategy>(); services.AddSingleton<ISortStrategy, MergeSortStrategy>(); services.AddSingleton<ISortStrategy, HeapSortStrategy>();
然后用 GetServices<isortstrategy>()</isortstrategy> 获取全部,或按名称解析(需配合 KeyedService):
services.AddKeyedSingleton<ISortStrategy, QuickSortStrategy>("quick");
services.AddKeyedSingleton<ISortStrategy, MergeSortStrategy>("merge");
运行时根据配置字符串选策略,比 if-else new 更易测、更易换。
- 不要在业务代码里 new 策略实例——这会让单元测试无法 mock 行为
- 若策略需传参(如超时时间),用工厂委托注册:
services.AddSingleton<isortstrategy>(sp => new QuickSortStrategy(timeout: 5000))</isortstrategy> - 注意作用域:无状态策略用
Singleton;带缓存或上下文的考虑Scoped,但要确认线程安全
Context 类只做策略分发,不掺杂算法逻辑
SortContext 这类上下文类,唯一职责是接收输入、选策略、调用 Execute(),绝不该出现 if (data.Length 这种分支逻辑——那已经是策略选择规则,应抽成独立的 <code>IStrategySelector。
典型反例:
public class SortContext
{
public void Sort(int[] data)
{
if (data.Length < 50) // ❌ 把选择逻辑写死在 Context 里
new InsertionSort().Sort(data);
else
new QuickSort().Sort(data);
}
}
正确做法是让 SortContext 依赖 IStrategySelector<isortstrategy></isortstrategy>,而选择器本身可基于数据特征、配置、甚至性能采样动态决定。
- Context 构造函数只接受策略接口或策略工厂,不接受具体实现类型
- 避免给 Context 加模板方法(如
BeforeSort()/AfterSort())——那是装饰器或管道的事,不是策略模式的职责 - 如果策略执行需统一异常处理或日志,用中间件或包装器(如
LoggingSortStrategy),别污染 Context
泛型策略基类容易误用,慎用 TStrategy 约束
有人试图用泛型约束统一策略族,比如 class StrategyContext<tstrategy> where TStrategy : class, IStrategy</tstrategy>,结果发现无法在运行时切换 TStrategy ——泛型参数是编译期确定的。这种写法实际把策略“固化”了,失去动态性。
真正需要泛型的地方,是策略接口本身支持类型参数(如 ISortStrategy<t></t>),且所有实现都针对同一组泛型参数;或者用泛型工厂方法封装创建逻辑。
- 别为了“看起来通用”而加泛型,先问:这个类型参数是否真会影响策略行为?是否所有策略实现都共用它?
- 若只是想让 Context 支持多种策略接口(如
ISortStrategy和ICompressStrategy),用接口继承或组合,而不是泛型类型参数 - 泛型策略类(如
SortStrategy<t></t>)适合做基类,但必须确保子类能被 DI 容器识别并注册,否则会因类型擦除导致解析失败
策略模式真正的复杂点不在语法,而在边界划分:什么算“算法”,什么算“策略选择逻辑”,什么算“上下文环境”。这三个层次一旦混在一起,再多的泛型和接口也救不了。










