策略模式详解
大约 10 分钟约 3087 字
策略模式详解
简介
策略模式(Strategy Pattern)是一种行为型设计模式,它定义了一系列算法,将每一个算法封装起来,并使它们可以互相替换。策略模式让算法的变化独立于使用算法的客户端。在 C# 中,策略模式与依赖注入、泛型和委托等特性结合,可以实现非常优雅和灵活的设计,常见于支付处理、排序策略、折扣计算等场景。
特点
基础策略模式实现
支付策略示例
// 策略接口
public interface IPaymentStrategy
{
string Name { get; }
Task<PaymentResult> PayAsync(decimal amount, PaymentInfo info);
}
public record PaymentInfo(string AccountId, string CustomerName);
public record PaymentResult(bool Success, string TransactionId, string Message);
// 支付宝策略
public class AlipayStrategy : IPaymentStrategy
{
public string Name => "支付宝";
public async Task<PaymentResult> PayAsync(decimal amount, PaymentInfo info)
{
Console.WriteLine($"[支付宝] 从账户 {info.AccountId} 扣款 {amount:C}");
await Task.Delay(100); // 模拟网络请求
return new PaymentResult(true, Guid.NewGuid().ToString("N")[..8], "支付宝支付成功");
}
}
// 微信支付策略
public class WeChatPayStrategy : IPaymentStrategy
{
public string Name => "微信支付";
public async Task<PaymentResult> PayAsync(decimal amount, PaymentInfo info)
{
Console.WriteLine($"[微信] 从账户 {info.AccountId} 扣款 {amount:C}");
await Task.Delay(100);
return new PaymentResult(true, Guid.NewGuid().ToString("N")[..8], "微信支付成功");
}
}
// 银行卡支付策略
public class BankCardStrategy : IPaymentStrategy
{
public string Name => "银行卡";
public async Task<PaymentResult> PayAsync(decimal amount, PaymentInfo info)
{
Console.WriteLine($"[银行] 从卡号 {info.AccountId} 扣款 {amount:C}");
await Task.Delay(200);
return new PaymentResult(true, Guid.NewGuid().ToString("N")[..8], "银行卡支付成功");
}
}
// 上下文 - 使用策略的客户端
public class PaymentService
{
private IPaymentStrategy _strategy;
public PaymentService(IPaymentStrategy strategy)
{
_strategy = strategy;
}
public void SetStrategy(IPaymentStrategy strategy)
{
_strategy = strategy;
}
public async Task<PaymentResult> ProcessPaymentAsync(decimal amount, PaymentInfo info)
{
Console.WriteLine($"使用 {_strategy.Name} 处理支付...");
return await _strategy.PayAsync(amount, info);
}
}排序策略示例
// 排序策略
public interface ISortStrategy<T> where T : IComparable<T>
{
string Name { get; }
List<T> Sort(List<T> items);
}
public class BubbleSortStrategy<T> : ISortStrategy<T> where T : IComparable<T>
{
public string Name => "冒泡排序";
public List<T> Sort(List<T> items)
{
var result = new List<T>(items);
for (int i = 0; i < result.Count - 1; i++)
for (int j = 0; j < result.Count - 1 - i; j++)
if (result[j].CompareTo(result[j + 1]) > 0)
(result[j], result[j + 1]) = (result[j + 1], result[j]);
return result;
}
}
public class QuickSortStrategy<T> : ISortStrategy<T> where T : IComparable<T>
{
public string Name => "快速排序";
public List<T> Sort(List<T> items)
{
var result = new List<T>(items);
QuickSort(result, 0, result.Count - 1);
return result;
}
private void QuickSort(List<T> list, int low, int high)
{
if (low < high)
{
int pivotIndex = Partition(list, low, high);
QuickSort(list, low, pivotIndex - 1);
QuickSort(list, pivotIndex + 1, high);
}
}
private int Partition(List<T> list, int low, int high)
{
var pivot = list[high];
int i = low - 1;
for (int j = low; j < high; j++)
{
if (list[j].CompareTo(pivot) <= 0)
{
i++;
(list[i], list[j]) = (list[j], list[i]);
}
}
(list[i + 1], list[high]) = (list[high], list[i + 1]);
return i + 1;
}
}
public class LinqSortStrategy<T> : ISortStrategy<T> where T : IComparable<T>
{
public string Name => "LINQ 排序";
public List<T> Sort(List<T> items)
{
return items.OrderBy(x => x).ToList();
}
}
// 排序上下文
public class Sorter<T> where T : IComparable<T>
{
private ISortStrategy<T> _strategy;
public Sorter(ISortStrategy<T> strategy) => _strategy = strategy;
public void SetStrategy(ISortStrategy<T> strategy) => _strategy = strategy;
public List<T> Execute(List<T> items)
{
Console.WriteLine($"使用 {_strategy.Name} 策略");
var sw = Stopwatch.StartNew();
var result = _strategy.Sort(items);
sw.Stop();
Console.WriteLine($"排序耗时: {sw.ElapsedMilliseconds}ms,元素数量: {items.Count}");
return result;
}
}DI 注入策略模式
在 ASP.NET Core 中,通过 DI 容器动态选择策略是最常见的实践方式。
// 策略解析器 - 通过 DI 容器管理所有策略
public interface IStrategyResolver
{
T GetStrategy<T>(string key) where T : class;
}
public class StrategyResolver : IStrategyResolver
{
private readonly IServiceProvider _serviceProvider;
private readonly Dictionary<Type, Dictionary<string, Type>> _strategyMap;
public StrategyResolver(IServiceProvider serviceProvider)
{
_serviceProvider = serviceProvider;
_strategyMap = new Dictionary<Type, Dictionary<string, Type>>();
}
public void Register<TInterface, TImplementation>(string key)
where TInterface : class
where TImplementation : class, TInterface
{
if (!_strategyMap.TryGetValue(typeof(TInterface), out var map))
{
map = new Dictionary<string, Type>();
_strategyMap[typeof(TInterface)] = map;
}
map[key] = typeof(TImplementation);
}
public T GetStrategy<T>(string key) where T : class
{
if (_strategyMap.TryGetValue(typeof(T), out var map) &&
map.TryGetValue(key, out var implementationType))
{
return (T)_serviceProvider.GetRequiredService(implementationType);
}
throw new InvalidOperationException($"未找到策略: {typeof(T).Name} - {key}");
}
}
// 扩展方法简化注册
public static class StrategyExtensions
{
public static IServiceCollection AddStrategy<TInterface, TImplementation>(
this IServiceCollection services, string key)
where TInterface : class
where TImplementation : class, TInterface
{
services.AddTransient<TImplementation>();
services.AddSingleton<IStrategyResolver>(sp =>
{
var resolver = sp.GetRequiredService<IStrategyResolver>();
resolver.Register<TInterface, TImplementation>(key);
return resolver;
});
return services;
}
}在 API 控制器中使用策略
// Program.cs 注册策略
var builder = WebApplication.CreateBuilder(args);
builder.Services.AddScoped<IStrategyResolver, StrategyResolver>();
builder.Services.AddTransient<AlipayStrategy>();
builder.Services.AddTransient<WeChatPayStrategy>();
builder.Services.AddTransient<BankCardStrategy>();
// 控制器
[ApiController]
[Route("api/[controller]")]
public class PaymentController : ControllerBase
{
private readonly IStrategyResolver _resolver;
public PaymentController(IStrategyResolver resolver)
{
_resolver = resolver;
}
[HttpPost("pay")]
public async Task<IActionResult> Pay([FromBody] PayRequest request)
{
// 根据请求中的支付方式动态选择策略
var strategy = request.PaymentMethod switch
{
"alipay" => _resolver.GetStrategy<IPaymentStrategy>("alipay"),
"wechat" => _resolver.GetStrategy<IPaymentStrategy>("wechat"),
"bank" => _resolver.GetStrategy<IPaymentStrategy>("bank"),
_ => throw new BadRequestException($"不支持的支付方式: {request.PaymentMethod}")
};
var result = await strategy.PayAsync(request.Amount,
new PaymentInfo(request.AccountId, request.CustomerName));
return result.Success ? Ok(result) : BadRequest(result);
}
}使用委托实现轻量策略
对于简单场景,可以使用委托(Func<T> / Action<T>)代替完整的策略类。
// 使用 Func 委托作为策略
public class PriceCalculator
{
private readonly Dictionary<string, Func<decimal, decimal>> _discountStrategies = new()
{
["none"] = price => price,
["percent10"] = price => price * 0.9m,
["percent20"] = price => price * 0.8m,
["fixed50"] = price => Math.Max(0, price - 50),
["buy2get1"] = price => price * 2 / 3, // 买三免一
};
public decimal Calculate(string strategy, decimal originalPrice)
{
if (_discountStrategies.TryGetValue(strategy, out var discountFunc))
return discountFunc(originalPrice);
throw new ArgumentException($"未知折扣策略: {strategy}");
}
public void RegisterStrategy(string name, Func<decimal, decimal> strategy)
{
_discountStrategies[name] = strategy;
}
}
// 使用
var calculator = new PriceCalculator();
calculator.RegisterStrategy("vip", price => price * 0.75m); // 动态注册
var finalPrice = calculator.Calculate("vip", 100m); // 75优点
策略模式高级应用
策略组合 — 责任链 + 策略
// 组合多个策略形成处理管道
public interface IDiscountStrategy
{
string Name { get; }
decimal Apply(decimal originalPrice, DiscountContext context);
int Priority { get; } // 优先级
}
public record DiscountContext(
string CustomerLevel,
DateTime OrderDate,
string PromoCode,
List<string> Tags);
// VIP 折扣策略
public class VipDiscountStrategy : IDiscountStrategy
{
public string Name => "VIP折扣";
public int Priority => 10;
public decimal Apply(decimal price, DiscountContext ctx)
{
return ctx.CustomerLevel switch
{
"gold" => price * 0.8m,
"silver" => price * 0.9m,
"diamond" => price * 0.7m,
_ => price
};
}
}
// 促销码策略
public class PromoCodeStrategy : IDiscountStrategy
{
public string Name => "促销码";
public int Priority => 20;
public decimal Apply(decimal price, DiscountContext ctx)
{
if (string.IsNullOrEmpty(ctx.PromoCode)) return price;
return ctx.PromoCode.ToUpper() switch
{
"SAVE10" => price * 0.9m,
"SAVE20" => price * 0.8m,
"HALF" => price * 0.5m,
_ => price
};
}
}
// 节日折扣策略
public class HolidayDiscountStrategy : IDiscountStrategy
{
public string Name => "节日折扣";
public int Priority => 30;
public decimal Apply(decimal price, DiscountContext ctx)
{
if (ctx.OrderDate.Month == 11 && ctx.OrderDate.Day >= 11
&& ctx.OrderDate.Day <= 11) // 双十一
return price * 0.7m;
if (ctx.OrderDate.Month == 12 && ctx.OrderDate.Day == 12) // 双十二
return price * 0.8m;
return price;
}
}
// 策略链 — 按优先级依次应用折扣
public class DiscountPipeline
{
private readonly List<IDiscountStrategy> _strategies;
public DiscountPipeline(IEnumerable<IDiscountStrategy> strategies)
{
_strategies = strategies.OrderBy(s => s.Priority).ToList();
}
public (decimal FinalPrice, List<string> AppliedStrategies) Calculate(
decimal originalPrice, DiscountContext context)
{
decimal price = originalPrice;
var applied = new List<string>();
foreach (var strategy in _strategies)
{
var newPrice = strategy.Apply(price, context);
if (newPrice < price)
{
applied.Add($"{strategy.Name}: {price:C} -> {newPrice:C}");
price = newPrice;
}
}
return (price, applied);
}
}
// 使用
var pipeline = new DiscountPipeline(new IDiscountStrategy[]
{
new VipDiscountStrategy(),
new PromoCodeStrategy(),
new HolidayDiscountStrategy()
});
var ctx = new DiscountContext("gold", new DateTime(2024, 11, 11), "SAVE10", new());
var (finalPrice, applied) = pipeline.Calculate(1000m, ctx);
Console.WriteLine($"最终价格: {finalPrice:C}");
applied.ForEach(Console.WriteLine);带策略工厂的模式
// 策略工厂 + 策略模式 — 通过配置创建策略
public interface IExportStrategy
{
string ContentType { get; }
byte[] Export<T>(IEnumerable<T> data);
}
public class CsvExportStrategy : IExportStrategy
{
public string ContentType => "text/csv";
public byte[] Export<T>(IEnumerable<T> data)
{
var sb = new StringBuilder();
var props = typeof(T).GetProperties();
sb.AppendLine(string.Join(",", props.Select(p => p.Name)));
foreach (var item in data)
{
sb.AppendLine(string.Join(",", props.Select(p => p.GetValue(item))));
}
return Encoding.UTF8.GetBytes(sb.ToString());
}
}
public class JsonExportStrategy : IExportStrategy
{
public string ContentType => "application/json";
public byte[] Export<T>(IEnumerable<T> data)
{
return JsonSerializer.SerializeToUtf8Bytes(data,
new JsonSerializerOptions { WriteIndented = true });
}
}
public class ExcelExportStrategy : IExportStrategy
{
public string ContentType => "application/vnd.openxmlformats-officedocument.spreadsheetml.sheet";
public byte[] Export<T>(IEnumerable<T> data)
{
Console.WriteLine($"导出 Excel: {data.Count()} 条记录");
return Array.Empty<byte>(); // 实际使用 EPPlus 等库
}
}
// 策略工厂
public static class ExportStrategyFactory
{
private static readonly Dictionary<string, IExportStrategy> _strategies = new()
{
["csv"] = new CsvExportStrategy(),
["json"] = new JsonExportStrategy(),
["excel"] = new ExcelExportStrategy()
};
public static IExportStrategy GetStrategy(string format)
{
if (_strategies.TryGetValue(format.ToLower(), out var strategy))
return strategy;
throw new ArgumentException($"不支持的导出格式: {format}");
}
public static IEnumerable<string> SupportedFormats => _strategies.Keys;
}
// 使用
var strategy = ExportStrategyFactory.GetStrategy("csv");
var data = new List<User> { new("张三", 25), new("李四", 30) };
var bytes = strategy.Export(data);策略模式与配置文件结合
// 通过 appsettings.json 配置策略选择
// appsettings.json:
// {
// "PaymentSettings": {
// "DefaultStrategy": "alipay",
// "Strategies": {
// "alipay": { "Enabled": true, "MinAmount": 0, "MaxAmount": 50000 },
// "wechat": { "Enabled": true, "MinAmount": 0, "MaxAmount": 30000 },
// "bank": { "Enabled": true, "MinAmount": 100, "MaxAmount": 1000000 }
// }
// }
// }
public class PaymentSettings
{
public string DefaultStrategy { get; set; }
public Dictionary<string, StrategyConfig> Strategies { get; set; }
}
public class StrategyConfig
{
public bool Enabled { get; set; }
public decimal MinAmount { get; set; }
public decimal MaxAmount { get; set; }
}
public class ConfigDrivenPaymentService
{
private readonly Dictionary<string, IPaymentStrategy> _strategies = new();
private readonly PaymentSettings _settings;
public ConfigDrivenPaymentService(
IEnumerable<IPaymentStrategy> strategies,
IOptions<PaymentSettings> settings)
{
_settings = settings.Value;
foreach (var strategy in strategies)
{
_strategies[strategy.Name] = strategy;
}
}
public IPaymentStrategy ResolveStrategy(decimal amount)
{
// 根据配置自动选择可用策略
foreach (var (name, config) in _settings.Strategies)
{
if (!config.Enabled) continue;
if (amount < config.MinAmount || amount > config.MaxAmount) continue;
if (_strategies.TryGetValue(name, out var strategy))
return strategy;
}
// 回退到默认策略
return _strategies[_settings.DefaultStrategy];
}
}策略模式与单元测试
// 策略模式天然适合单元测试 — 可以独立测试每个策略
[TestClass]
public class PricingStrategyTests
{
[TestMethod]
public void VipDiscount_GoldCustomer_Applies20PercentOff()
{
// Arrange
var strategy = new VipDiscountStrategy();
var ctx = new DiscountContext("gold", DateTime.Now, "", new());
// Act
var result = strategy.Apply(100m, ctx);
// Assert
Assert.AreEqual(80m, result);
}
[TestMethod]
public void PromoCode_InvalidCode_ReturnsOriginalPrice()
{
var strategy = new PromoCodeStrategy();
var ctx = new DiscountContext("normal", DateTime.Now, "INVALID", new());
var result = strategy.Apply(100m, ctx);
Assert.AreEqual(100m, result);
}
[TestMethod]
public void DiscountPipeline_AppliesMultipleStrategies_Correctly()
{
var pipeline = new DiscountPipeline(new IDiscountStrategy[]
{
new VipDiscountStrategy(),
new PromoCodeStrategy()
});
var ctx = new DiscountContext("gold", DateTime.Now, "SAVE10", new());
var (finalPrice, _) = pipeline.Calculate(100m, ctx);
// VIP 折扣 100 * 0.8 = 80, 再促销码 80 * 0.9 = 72
Assert.AreEqual(72m, finalPrice);
}
}缺点
总结
策略模式是消除条件分支、提高代码灵活性的利器。在 C# 中,从简单的委托字典到完整的 DI 注入策略解析器,可以根据项目复杂度选择合适的实现方式。关键是识别出代码中因算法变化而频繁修改的条件分支,将其提取为独立的策略。结合 .NET 依赖注入使用策略模式时,可以充分利用泛型和反射实现自动化注册,大幅减少样板代码。
关键知识点
- 模式不是目标,降低耦合和控制变化才是目标。
- 先找变化点、稳定点和协作边界,再决定是否引入模式。
- 同一个模式在不同规模下的收益和代价差异很大。
项目落地视角
- 优先画出参与对象、依赖方向和调用链,再落到代码。
- 把模式放到一个真实场景里,比如支付、规则引擎、工作流或插件扩展。
- 配合单元测试或契约测试,保证重构后的行为没有漂移。
常见误区
- 为了看起来“高级”而套模式。
- 把简单问题拆成过多抽象层,导致阅读和排障都变难。
- 只会背 UML,不会解释为什么这里需要这个模式。
进阶路线
- 继续关注模式之间的组合用法,而不是孤立记忆。
- 从业务建模、演进策略和团队协作角度看模式的适用性。
- 把模式结论沉淀为项目模板、基类或约束文档。
适用场景
- 当你准备把《策略模式详解》真正落到项目里时,最适合先在一个独立模块或最小样例里验证关键路径。
- 适合在业务规则频繁变化、分支增多或对象协作复杂时引入。
- 当你希望提高扩展性,但又不想把系统拆得过度抽象时,这类主题很有参考价值。
落地建议
- 先识别变化点,再决定是否引入模式,而不是反过来套模板。
- 优先为模式的边界、依赖和调用路径画出简单结构图。
- 把模式落到一个明确场景,例如支付、规则计算、插件扩展或工作流。
排错清单
- 检查抽象层是否过多,导致调用路径和责任不清晰。
- 确认引入模式后是否真的减少了条件分支和重复代码。
- 警惕“为了模式而模式”,尤其是在简单业务里。
复盘问题
- 如果把《策略模式详解》放进你的当前项目,最先要验证的输入、输出和失败路径分别是什么?
- 《策略模式详解》最容易在什么规模、什么边界条件下暴露问题?你会用什么指标或日志去确认?
- 相比默认实现或替代方案,采用《策略模式详解》最大的收益和代价分别是什么?
