模式简介
策略模式是一种行为设计模式,适用于我们需要针对不同的状况采用不同的行为策略的情景,通常对于这种情景我们会优先考虑到使用条件语句来管理,但是这么做并不利于后续的扩展以及难以应对状况繁多复杂的情况,对于简单的情景可以通过条件语句来实现。
策略模式将具体使用权交给调用者,因为每个策略会被要求实现统一的接口,所以策略管理的主体可以根据调用者传递的指定的策略,而在不修改内部代码的情况下通过接口方法采用统一的方式对不同的策略进行调用。
常见的应用场景:排序算法、图像处理、支付方式、日志记录、游戏开发、缓存策略、路由策略等。
特征 | 策略模式 | 条件语句 |
---|---|---|
可维护性 | 高,每个算法有独立的策略类,易于新增、修改和删除算法。 | 低,条件语句通常散布在代码中,修改可能需要多处修改。 |
可扩展性 | 高,添加新的算法只需要实现新的策略类,不影响现有代码。 | 低,添加新的条件通常需要修改包含条件语句的函数或类。 |
复用性 | 高,每个策略类都是独立的,可以在不同的上下文中被复用。 | 低,条件语句通常紧密耦合于特定的类或函数,难以单独复用。 |
可读性 | 高,通过命名清晰的策略类,代码更容易理解和维护。 | 低,复杂的条件语句可能导致代码难以理解,降低可读性。 |
灵活性 | 高,可以在运行时动态切换算法,灵活应对变化。 | 低,通常需要修改源代码,不够灵活,容易引入错误。 |
依赖关系 | 低,策略模式降低了上下文和具体策略之间的耦合。 | 高,条件语句使得代码模块之间的依赖性增加。 |
性能影响 | 通常较小,因为策略模式将算法封装在独立的对象中。 | 取决于条件语句的复杂性,可能会导致性能损失。 |
模式结构
-
Context(上下文): 定义了一个接口,用于维护对策略对象的引用,并负责在运行时切换不同的策略。上下文通常持有一个策略对象的引用,并通过策略对象执行具体的算法。
-
Strategy(策略): 定义了一组算法接口,通常通过接口或抽象类来实现。这个接口定义了具体策略类必须实现的方法。
-
ConcreteStrategy(具体策略): 实现了策略接口的具体算法。每个具体策略类实现了算法的一种变体。
工作原理
-
客户端使用Context设置策略: 客户端通过Context对象设置所需的具体策略,可以在运行时动态切换策略。
-
Context调用策略算法: 当客户端调用Context的方法时,Context会将具体的算法委托给当前设置的策略对象来执行。
-
具体策略类执行算法: 具体策略类实现了策略接口定义的算法,通过Context调用的方式执行特定的算法。
-
客户端与具体算法解耦: 策略模式的关键在于将客户端与具体算法的实现解耦。客户端通过接口与Context交互,而具体策略类的变化不会影响客户端。
代码示例(C#)
提示:可在本栏目的资源篇“设计模式代码示例合集”下载所有完整代码资源。
动物:Animal.cs
namespace StrategyPattern;
// 动物
class Animal
{
public string name; // 动物名称
public float weight; // 动物体重,单位kg
public float length; // 动物从头部到尾部的长度,单位m
public Animal(string name, float weight, float length)
{
this.name = name;
this.weight = weight;
this.length = length;
}
public override string ToString()
{
return $"[name:{name},weight:{weight},length:{length}]";
}
}
// 猫
class Cat : Animal
{
public Cat(string name, float weight, float length) : base(name, weight, length) { }
}
策略:Strategy.cs
namespace StrategyPattern;
// 策略
interface IStrategy
{
// 约定返回结果:-1表示a劣于b,0表示a与b平等,1表示a优于b
int Compare(Animal a, Animal b);
}
// 体重比较策略
class WeightStrategy : IStrategy
{
public int Compare(Animal a, Animal b)
{
if (a != null && b != null)
{
// 动物a的体重低于动物b
if (a.weight < b.weight) return -1;
// 动物a的体重高于动物b
if (a.weight > b.weight) return 1;
}
return 0;
}
}
// 长度比较策略
class LengthStrategy : IStrategy
{
public int Compare(Animal a, Animal b)
{
if (a != null && b != null)
{
// 动物a的长度小于动物b
if (a.length < b.length) return -1;
// 动物a的长度大于动物b
if (a.length > b.length) return 1;
}
return 0;
}
}
策略模式:StrategyPattern.cs
namespace StrategyPattern;
// 策略模式
class StrategyPatternManager
{
private IStrategy strategy;
public StrategyPatternManager(IStrategy strategy)
{
this.strategy = strategy;
}
public void Sort(Animal[] animals, bool minToMax = true)
{
// 符合以下判断则不进行排序
if (strategy == null || animals == null || animals.Length == 0) return;
// res用于记录比较策略的结果值,1表示从小到大排序,-1表示从大到小排序
int res = 1;
if (!minToMax) res = -1;
// 冒泡排序
for (int i = 0; i < animals.Length; i++)
{
for (int j = i + 1; j < animals.Length; j++)
{
if (strategy.Compare(animals[i], animals[j]) == res)
{
Animal temp = animals[i];
animals[i] = animals[j];
animals[j] = temp;
}
}
}
}
}
测试代码:Program.cs
// ************* 5.策略模式测试 **************
using StrategyPattern;
Animal[] cats =
{
new Cat("橘猫",20f,0.78f),
new Cat("狸花猫",15f,0.72f),
new Cat("英国短尾猫",18f,0.65f),
};
WeightStrategy weightStrategy = new WeightStrategy();
LengthStrategy lengthStrategy = new LengthStrategy();
Console.WriteLine("*************** 按照体重从小到大对所有猫进行排序并输出 *****************");
StrategyPatternManager strategyPattern = new StrategyPatternManager(weightStrategy);
strategyPattern.Sort(cats);
for (int i = 0; i < cats.Length; i++)
{
Console.WriteLine(cats[i]);
}
Console.WriteLine("*************** 按照长度从大到小对所有猫进行排序并输出 *****************");
strategyPattern = new StrategyPatternManager(lengthStrategy);
strategyPattern.Sort(cats, false);
for (int i = 0; i < cats.Length; i++)
{
Console.WriteLine(cats[i]);
}
代码解说
上述示例我们通过策略模式实现对所有猫分别按照体重和长度进行排序和输出。这里策略模式的应用则是针对猫的体重和长度的比较策略,二者都实现了策略接口,具体比较策略的逻辑由各自编写,StrategyPatternManager提供排序调用接口,根据创建实例时传递的比较策略来对Animal数组进行排序。这里之所以让Cat继承Animal是为了便于扩展,可能后续存在其他类型动物以及不同类型动物之间的比较需求。
如果这篇文章对你有帮助,请给作者点个赞吧!