- 什么是策略模式? (定义与意图)
- 为什么要使用策略模式? (解决的问题与优势)
- 策略模式的结构与参与者 (UML类图与角色讲解)
- C#代码实现示例 (一个生动且完整的例子)
- 策略模式的优缺点 (全面客观地评价)
- 在C#中的实际应用场景
- 策略模式与其他模式的区别 (避免混淆)
1. 什么是策略模式?
策略模式是一种行为型设计模式。它的核心思想是:定义一系列算法,把它们一个个封装起来,并且使它们可相互替换。
简单来说,策略模式让算法的变化独立于使用算法的客户。这意味着,当需要改变或增加一种算法时,你不需要修改使用算法的客户端代码,只需要增加一个新的策略实现即可。
一个生活中的比喻:
假设你要去旅游,你可以选择不同的交通方式:坐飞机、坐火车、或者自己开车。
- “去旅游” 是你的上下文。
- “交通方式” 是你的策略。
- “飞机”、“火车”、“汽车” 是具体策略。
你可以根据预算、时间等因素,随时更换你的交通方式(策略),但“去旅游”这个目标(上下文)是不变的。
2. 为什么要使用策略模式?
想象一个没有策略模式的场景:你有一个ShoppingCart(购物车)类,它需要根据不同的用户类型计算折扣。
// 糟糕的设计:违反了开闭原则
public class ShoppingCart
{
    public decimal CalculatePrice(decimal originalPrice, string userType)
    {
        switch (userType)
        {
            case "Normal":
                return originalPrice;
            case "VIP":
                return originalPrice * 0.8m;
            case "SuperVIP":
                return originalPrice * 0.5m;
            default:
                return originalPrice;
        }
    }
}这种设计的问题:
- 违反开闭原则:每当需要增加一种新的用户类型(比如EmployeeVIP),你就必须修改ShoppingCart类的CalculatePrice方法。这增加了引入bug的风险,也使得代码难以维护。
- 代码臃肿:所有的折扣逻辑都堆积在一个类里,如果计算逻辑变得复杂,这个方法会变得异常庞大和难以阅读。
- 算法与上下文耦合:折扣算法和购物车本身紧密地绑定在一起。
策略模式如何解决这个问题?
策略模式将“计算折扣”这个行为从ShoppingCart类中抽离出来,封装成独立的策略类。ShoppingCart类只需要持有一个策略的引用,并在需要时调用它即可。
3. 策略模式的结构与参与者
策略模式通常包含三个核心角色:
- IStrategy(策略接口/抽象类)- 定义了一个所有具体策略都必须实现的公共接口。这个接口就是上下文用来调用算法的“契约”。
 
- ConcreteStrategy(具体策略类)- 实现了IStrategy接口,提供了具体的算法实现。例如:NormalDiscount,VIPDiscount,SuperVIPDiscount。
 
- 实现了
- Context(上下文类)- 持有一个对IStrategy对象的引用。
- 它可以定义一个接口来让Strategy访问它的数据。
- 它的客户端通常在创建时或运行时向其传递一个具体的Strategy对象。
- 它不关心具体是哪个策略,只负责调用策略接口定义的方法。
 
- 持有一个对
UML 类图:
+----------------+       +------------------+
|    Context     |------>|   IStrategy      |
+----------------+       +------------------+
| - strategy     |       | + Execute()      |
+----------------+       +------------------+
| + SetStrategy()|               ^
| + DoSomething()|               |
+----------------+       +-------+-------+
                        | ConcreteStrategyA |
                        +-------------------+
                        | + Execute()       |
                        +-------------------+
                        | ConcreteStrategyB |
                        +-------------------+
                        | + Execute()       |
                        +-------------------+4. C#代码实现示例
我们用上面购物车折扣的例子来完整实现策略模式。
步骤 1: 定义策略接口 IDiscountStrategy
这个接口定义了所有折扣策略必须遵守的契约。
// IStrategy: 策略接口
public interface IDiscountStrategy
{
    // 计算折扣后的价格
    decimal CalculateDiscount(decimal originalPrice);
}步骤 2: 创建具体策略类
为每种折扣规则创建一个独立的类。
// ConcreteStrategyA: 普通用户无折扣
public class NormalDiscountStrategy : IDiscountStrategy
{
    public decimal CalculateDiscount(decimal originalPrice)
    {
        Console.WriteLine("使用普通用户折扣:无折扣");
        return originalPrice;
    }
}
// ConcreteStrategyB: VIP用户8折
public class VipDiscountStrategy : IDiscountStrategy
{
    public decimal CalculateDiscount(decimal originalPrice)
    {
        Console.WriteLine("使用VIP用户折扣:8折");
        return originalPrice * 0.8m;
    }
}
// ConcreteStrategyC: 超级VIP用户5折
public class SuperVipDiscountStrategy : IDiscountStrategy
{
    public decimal CalculateDiscount(decimal originalPrice)
    {
        Console.WriteLine("使用超级VIP用户折扣:5折");
        return originalPrice * 0.5m;
    }
}步骤 3: 创建上下文类 ShoppingCart
上下文类持有一个策略对象,并利用它来执行计算。
// Context: 上下文类
public class ShoppingCart
{
    // 持有一个策略接口的引用
    private IDiscountStrategy _discountStrategy;
    // 构造函数中注入策略
    public ShoppingCart(IDiscountStrategy discountStrategy)
    {
        _discountStrategy = discountStrategy;
    }
    // 允许在运行时更换策略
    public void SetDiscountStrategy(IDiscountStrategy discountStrategy)
    {
        _discountStrategy = discountStrategy;
    }
    // 上下文的方法,它会将具体工作委托给策略
    public decimal Checkout(decimal originalPrice)
    {
        // ... 其他购物车逻辑,如计算总价、运费等 ...
        
        // 委托给策略对象来计算折扣
        return _discountStrategy.CalculateDiscount(originalPrice);
    }
}步骤 4: 客户端代码
客户端负责创建具体的策略对象,并将其传递给上下文。
public class Program
{
    public static void Main(string[] args)
    {
        decimal originalPrice = 1000m;
        // 场景1:一个普通用户结账
        Console.WriteLine("--- 普通用户结账 ---");
        var normalStrategy = new NormalDiscountStrategy();
        var cart1 = new ShoppingCart(normalStrategy);
        decimal finalPrice1 = cart1.Checkout(originalPrice);
        Console.WriteLine($"最终价格: {finalPrice1:C}\n");
        // 场景2:一个VIP用户结账
        Console.WriteLine("--- VIP用户结账 ---");
        var vipStrategy = new VipDiscountStrategy();
        var cart2 = new ShoppingCart(vipStrategy);
        decimal finalPrice2 = cart2.Checkout(originalPrice);
        Console.WriteLine($"最终价格: {finalPrice2:C}\n");
        // 场景3:用户在结账前突然升级为超级VIP
        Console.WriteLine("--- 用户升级为超级VIP后结账 ---");
        var superVipStrategy = new SuperVipDiscountStrategy();
        // 运行时动态切换策略
        cart2.SetDiscountStrategy(superVipStrategy);
        decimal finalPrice3 = cart2.Checkout(originalPrice);
        Console.WriteLine($"最终价格: {finalPrice3:C}\n");
    }
}输出结果:
--- 普通用户结账 ---
使用普通用户折扣:无折扣
最终价格: ¥1,000.00
--- VIP用户结账 ---
使用VIP用户折扣:8折
最终价格: ¥800.00
--- 用户升级为超级VIP后结账 ---
使用超级VIP用户折扣:5折
最终价格: ¥500.00这个例子完美地展示了策略模式的威力:算法(折扣逻辑)与使用算法的上下文(购物车)解耦,并且可以在运行时灵活切换。
5. 策略模式的优缺点
优点:
- 符合开闭原则:你可以在不修改现有代码(上下文类)的情况下,引入新的策略(只需添加新的策略类)。
- 避免使用多重条件语句:将switch-case或if-else链消除,使代码更清晰、更易于维护。
- 算法可以自由切换:由于策略实现了同一接口,它们之间可以互相替换。
- 扩展性良好:增加新策略非常容易,符合高内聚、低耦合的设计原则。
- 提高了代码的复用性:每个策略都是一个独立的类,可以在其他需要相同算法的地方复用。
缺点:
- 策略类数量会增加:每增加一个策略,就要增加一个新的类。当策略很多时,可能会导致类的数量爆炸。
- 所有策略类都需要对外暴露:客户端必须知道所有的策略类,并自行决定使用哪一个。这增加了客户端的复杂度。
- 策略之间可能存在共享数据:如果不同策略之间需要共享一些状态,那么就需要在上下文中进行管理,这会增加上下文的复杂性。
6. 在C#中的实际应用场景
策略模式在C#开发中非常常见,很多框架和库都隐式或显式地使用了它。
- ASP.NET Core 中间件管道:你可以决定使用哪些认证中间件(如Cookie认证、JWT Bearer认证、OpenID Connect认证),每种认证方式就是一种策略。
- 依赖注入:DI容器本身就是一个巨大的策略工厂。当你为一个接口注册不同的实现时,你就是在配置使用哪种“策略”。
- 排序算法:Array.Sort()或List.Sort()方法可以接受一个IComparer接口的实现。IComparer就是一个策略接口,你可以提供不同的比较策略来定义自定义排序。
- 数据验证:在ASP.NET Core中,你可以为同一个模型属性添加多个验证特性(如[Required],[StringLength],[RegularExpression]),每个验证特性都是一个独立的验证策略。
- 日志记录:你可以配置不同的日志提供程序(如Console、Debug、File、Azure App Service),每个提供程序就是一种记录日志的策略。
7. 策略模式与其他模式的区别
- 策略模式 vs. 状态模式 - 意图不同:策略模式是为了让算法可以互换;状态模式是为了让一个对象在其内部状态改变时,改变其行为。
- 关系不同:策略模式中,客户端通常主动选择并设置策略;状态模式中,状态对象会自行在上下文中切换。
- 耦合度:策略模式中,策略与上下文通常是独立的;状态模式中,状态对象之间常常有关联和转换。
 
- 策略模式 vs. 模板方法模式 - 继承 vs. 组合:模板方法模式基于继承,它在父类中定义算法骨架,子类实现具体步骤;策略模式基于组合,它将整个算法封装在对象中,并让上下文持有该对象。
- 改变点不同:模板方法改变的是算法的局部步骤;策略模式改变的是整个算法。
 
总结
策略模式是一个非常基础且强大的设计模式,它通过封装变化,将算法的调用者和实现者解耦。当你遇到一系列具有相似行为但实现方式不同的场景时,特别是当你需要避免使用大量的if-else或switch-case语句时,策略模式是一个非常值得考虑的优秀解决方案。它使你的代码更加灵活、可扩展和易于维护。