Strategy Pattern (策略模式)
所谓 Strategy Pattern 的精神就是将策略 (算法) 封装为一个对象易于相互替换如同 USB 设备一样可即插即用而不是将策略具体的算法和行为硬编码在某个类或客户程序中导至事后的修改和扩展不易
若有多种「策略」就将这些个策略和这些策略的算法行为封装在各个类中并让这些类去继承某个公用的抽象类或接口接着在客户程序中就可动态引用且易于更换这些不同的「策略」不会因为日后添加修改了某一个「策略」就得重新修改编译多处的源代码此即为一种「封装变化点」的做法将常会变化的部分进行抽象定义为接口亦即实现「面向接口编程」的概念且客户程序 (调用者) 只须知道接口的外部定义即可具体的实现则无须理会
The Strategy Pattern defines a family of algorithms encapsulates each one and makes them interchangeable Strategy lets the algorithm vary independently from clients that use it
- Design Patterns: Elements of Reusable ObjectOriented Software
Strategy Pattern 适用的情景
应用中的许多类在解决某些问题时很相似但实现的行为有所差异比如不同功能的程序都可能要用到「排序」算法
根据运行环境的不同需要采用不同的算法比如在手机PC 计算机上因硬件等级不同必须采用不同的排序算法
针对给定的目的存在多种不同的算法且我们可用代码实现算法选择的标准
需要封装复杂的数据结构比如特殊的加密算法客户程序仅需要知道调用的方式即可
同上算法中的罗辑和使用的数据应该与客户程序隔离时
图 这张为很多书籍和文档都曾出现过的 Strategy 经典 Class Diagram
_Shellaspxcs
using System;
using blogsWizardWusample;
//客户程序
public partial class __Shell : SystemWebUIPage
{
protected void Page_Load(object sender EventArgs e)
{
//执行对象
Context context;
context = new Context(new ConcreteStrategyA());
ResponseWrite(contextContextInterface() +
);
context = new Context(new ConcreteStrategyB());
ResponseWrite(contextContextInterface() +
);
context = new Context(new ConcreteStrategyC());
ResponseWrite(contextContextInterface() +
);
}
}
namespace blogsWizardWusample
{
//抽象算法类 (亦可用接口)定义了所有策略的公共接口
abstract class Strategy
{
//算法需要完成的功能
public abstract string AlgorithmInterface();
}
//具体算法类A
class ConcreteStrategyA : Strategy
{
//算法A实现方法
public override string AlgorithmInterface()
{
return 算法A实现;
}
}
//具体算法类B
class ConcreteStrategyB : Strategy
{
//算法B实现方法
public override string AlgorithmInterface()
{
return 算法B实现;
}
}
//具体算法类C
class ConcreteStrategyC : Strategy
{
//算法C实现方法
public override string AlgorithmInterface()
{
return 算法C实现;
}
}
//执行对象需要采用可替换策略执行的对象
class Context
{
Strategy strategy;
public Context(Strategy strategy) //构造函数
{
thisstrategy = strategy;
}
//执行对象依赖于策略对象的操作方法
public string ContextInterface()
{
return strategyAlgorithmInterface();
}
}
} // end of namespace
/*
结行结果:
算法A实现
算法B实现
算法C实现
*/
上方的「Shell (壳)」示例中最下方的 Context 类为一种维护上下文信息的类让 Strategy 类 (或 IStrategy 接口) 及其子类对象的算法能运行在这个上下文里
下方的图 及其代码为此 Shell 示例和 Strategy Pattern 的一个具体实现示例我们知道Linux 和 Windows 操作系统在文本文件的「换行符」是不同的前者为「\n」后者为「\r\n」若我们要设计一个文本编辑工具或简易的编程工具必须要能随时转换这两种不同操作系统的换行符 (假设 NET 已可执行于 Linux 上)此时我们即不该在客户程序 (如ASPNET 页面的 CodeBehind) 中用硬编码 switchcase 的 hard coding 写法而应如下方示例以 Strategy Pattern 实现此一功能并将这些算法 (策略) 各自封装在各个子类中 (如 ASPNET 项目的 App_Code 文件夹中的类或其他类库项目中的类)使他们易于组合更换便于日后的维护和修改
图 示例 _Strategyaspxcs 的 Class Diagram此为 Sybase PowerDesigner 的「Reverse Engineer」功能所自动产生的图
_Strategyaspxcs
using System;
using blogsWizardWusample;
//客户程序
public partial class __Strategy : SystemWebUIPage
{
String strLinuxText = 操作系统 \n 红帽 Linux 创建的 \n 文本文件;
String strWindowsText = 操作系统 \r\n 微软 Windows 创建的 \r\n 文本文件;
protected void Page_Load(object sender EventArgs e)
{
}
protected void DropDownList_SelectedIndexChanged(object sender EventArgs e)
{
switch(DropDownListSelectedValue)
{
case Linux:
LabelText = CntextInterface(new LinuxStrategy(strWindowsText));
//LabelText = strWindowsTextReplace(\r\n \n); //未用任何 Pattern 的写法
break;
case Windows:
LabelText = CntextInterface(new WindowsStrategy(strLinuxText));
//LabelText = strLinuxTextReplace(\n \r\n); //未用任何 Pattern 的写法
break;
default:
LabelText = StringEmpty;
break;
}
}
}
namespace blogsWizardWusample
{
//抽象算法类 (亦可用接口)定义了所有策略的公共接口
public abstract class TextStrategy
{
protected String text;
public TextStrategy(String text) //构造函数
{
thistext = text;
}
//算法需要完成的功能
public abstract String replaceChar();
}
//具体算法类A
public class LinuxStrategy : TextStrategy
{
public LinuxStrategy(String text) //构造函数
: base(text)
{
}
//算法A实现方法
public override String replaceChar()
{
text = textReplace(\r\n \n);
return text;
}
}
//具体算法类B
public class WindowsStrategy : TextStrategy
{
public WindowsStrategy(String text) //构造函数
: base(text)
{
}
//算法B实现方法
public override String replaceChar()
{
text = textReplace(\n \r\n);
return text;
}
}
//执行对象需要采用可替换策略执行的对象
public class ContextCharChange
{
//执行对象依赖于策略对象的操作方法
public static String contextInterface(TextStrategy strategy)
{
return strategyreplaceChar();
}
}
} // end of namespace
图 示例 _Strategyaspxcs 的执行结果
若未用任何 Pattern 的客户程序可能就如下方的硬编码将「换行符」和算法直接写死在 ASPNET 的 CodeBehind 里导至事后的维护和扩展不易
hard coding
protected void DropDownList_SelectedIndexChanged(object sender EventArgs e)
{
switch(DropDownListSelectedValue)
{
case Linux:
LabelText = strWindowsTextReplace(\r\n \n);
break;
case Windows:
LabelText = strLinuxTextReplace(\n \r\n);
break;
default:
LabelText = StringEmpty;
break;
}
}
此外若用 Simple Factory Pattern (简单工厂模式) 虽然也能解决上述硬编码的问题但就如我们前一篇帖子「C# Design Patterns () Factory Method」曾经提过的缺点日后若要添加或修改功能时仍要修改重新编译 serverside 的「工厂类」所以在此种情况下用 Strategy 会是比 Simple Factory 更好的选择
Strategy Pattern 的优点
简化了单元测试因为每个算法都有自己的类可以通过自己的接口单独做测试
避免程序中使用多重条件转移语句使系统更灵活并易于扩展
高内聚低偶合
Strategy Pattern 的缺点
因为每个具体策略都会产生一个新类所以会增加需要维护的类的数量
选择所用具体实现的职责由客户程序承担并转给 Context 对象并没有解除客户端需要选择判断的压力
若要减轻客户端压力或程序有特殊考量还可把 Strategy 与 Simple Factory 两种 Pattern 结合即可将选择具体算法的职责改由 Context 来承担亦即将具体的算法和客户程序做出隔离有关这方面的概念和示例可参考伍迷的「大话设计模式」一书 []
此外从行为上来看State Pattern 和 Strategy Pattern 有点类似但前者可看作后者的动态版本
State看当前是什么状态就采取什么动作
Strategy看需求及情景为何采用适当的策略
State 中当对象内部的状态改变时它可切换到一组不同的操作从而改变对象的行为例如 GoF 示例中的 TCP 连接而 Strategy 是直接采用适当的策略 (算法)如本帖示例中不同的操作系统实现换行的具体算法类 LinuxStrategy 与 WindowsStrategy