1. 什么是策略模式? (定义与意图)
  2. 为什么要使用策略模式? (解决的问题与优势)
  3. 策略模式的结构与参与者 (UML类图与角色讲解)
  4. C#代码实现示例 (一个生动且完整的例子)
  5. 策略模式的优缺点 (全面客观地评价)
  6. 在C#中的实际应用场景
  7. 策略模式与其他模式的区别 (避免混淆)

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. 策略模式的优缺点

优点:

  1. 符合开闭原则:你可以在不修改现有代码(上下文类)的情况下,引入新的策略(只需添加新的策略类)。
  2. 避免使用多重条件语句:将switch-caseif-else链消除,使代码更清晰、更易于维护。
  3. 算法可以自由切换:由于策略实现了同一接口,它们之间可以互相替换。
  4. 扩展性良好:增加新策略非常容易,符合高内聚、低耦合的设计原则。
  5. 提高了代码的复用性:每个策略都是一个独立的类,可以在其他需要相同算法的地方复用。

缺点:

  1. 策略类数量会增加:每增加一个策略,就要增加一个新的类。当策略很多时,可能会导致类的数量爆炸。
  2. 所有策略类都需要对外暴露:客户端必须知道所有的策略类,并自行决定使用哪一个。这增加了客户端的复杂度。
  3. 策略之间可能存在共享数据:如果不同策略之间需要共享一些状态,那么就需要在上下文中进行管理,这会增加上下文的复杂性。

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-elseswitch-case语句时,策略模式是一个非常值得考虑的优秀解决方案。它使你的代码更加灵活、可扩展和易于维护。

最后修改:2025 年 10 月 30 日
如果觉得我的文章对你有用,请随意赞赏