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 Object-Oriented Software





Strategy Pattern 适用的情景:

  • 应用中的许多类,在解决某些问题时很相似,但实现的行为有所差异。比如:不同功能的程序,都可能要用到「排序」算法。
  • 根据运行环境的不同,需要采用不同的算法。比如:在手机、PC 计算机上,因硬件等级不同,必须采用不同的排序算法。
  • 针对给定的目的,存在多种不同的算法,且我们可用代码实现算法选择的标准。
  • 需要封装复杂的数据结构。比如:特殊的加密算法,客户程序仅需要知道调用的方式即可。
  • 同上,算法中的罗辑和使用的数据,应该与客户程序隔离时。

 Strategy Pattern 图解:

 

using System; using com.cnblogs.WizardWu.sample01; //客户程序 public partial class _01_Shell : System.Web.UI.Page { protected void Page_Load(object sender, EventArgs e) { //执行对象 Context context; context = new Context(new ConcreteStrategyA()); Response.Write(context.ContextInterface() + "<br>"); context = new Context(new ConcreteStrategyB()); Response.Write(context.ContextInterface() + "<br>"); context = new Context(new ConcreteStrategyC()); Response.Write(context.ContextInterface() + "<br>"); } } namespace com.cnblogs.WizardWu.sample01 { //抽象算法类 (亦可用接口)。定义了所有策略的公共接口 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) //构造函数 { this.strategy = strategy; } //执行对象依赖于策略对象的操作方法 public string ContextInterface() { return strategy.AlgorithmInterface(); } } } // end of namespace /* 结行结果: 算法A实现 算法B实现 算法C实现 */   

 

     上方的「Shell (壳)」示例中,最下方的 Context 类,为一种维护上下文信息的类,让 Strategy 类 (或 IStrategy 接口) 及其子类对象的算法,能运行在这个上下文里。

 

 

     下方的图 2 及其代码,为此 Shell 示例和 Strategy Pattern 的一个具体实现示例。我们知道,Linux 和 Windows 操作系统,在文本文件的「换行符」是不同的,前者为「/n」,后者为「/r/n」。若我们要设计一个文本编辑工具,或简易的编程工具,必须要能随时转换这两种不同操作系统的换行符 (假设 .NET 已可执行于 Linux 上)。此时我们即不该在客户程序 (如:ASP.NET 页面的 Code-Behind) 中用硬编码 switch...case 的 hard coding 寫法,而应如下方示例,以 Strategy Pattern 实现此一功能,并将这些算法 (策略) 各自封装在各个子类中 (如 ASP.NET 项目的 App_Code 文件夹中的类,或其他类库项目中的类),使他们易于组合、更换,便于日后的维护和修改。

 

 

 

using System; using com.cnblogs.WizardWu.sample02; //客户程序 public partial class _02_Strategy : System.Web.UI.Page { String strLinuxText = "操作系统 /n 红帽 Linux 创建的 /n 文本文件"; String strWindowsText = "操作系统 /r/n 微软 Windows 创建的 /r/n 文本文件"; protected void Page_Load(object sender, EventArgs e) { } protected void DropDownList1_SelectedIndexChanged(object sender, EventArgs e) { switch(DropDownList1.SelectedValue) { case "Linux": Label1.Text = ContextCharChange.contextInterface(new LinuxStrategy(strWindowsText)); //Label1.Text = strWindowsText.Replace("/r/n", "/n"); //未用任何 Pattern 的写法 break; case "Windows": Label1.Text = ContextCharChange.contextInterface(new WindowsStrategy(strLinuxText)); //Label1.Text = strLinuxText.Replace("/n", "/r/n"); //未用任何 Pattern 的写法 break; default: Label1.Text = String.Empty; break; } } } namespace com.cnblogs.WizardWu.sample02 { //抽象算法类 (亦可用接口)。定义了所有策略的公共接口 public abstract class TextStrategy { protected String text; public TextStrategy(String text) //构造函数 { this.text = text; } //算法需要完成的功能 public abstract String replaceChar(); } //具体算法类A public class LinuxStrategy : TextStrategy { public LinuxStrategy(String text) //构造函数 : base(text) { } //算法A实现方法 public override String replaceChar() { text = text.Replace("/r/n", "/n"); return text; } } //具体算法类B public class WindowsStrategy : TextStrategy { public WindowsStrategy(String text) //构造函数 : base(text) { } //算法B实现方法 public override String replaceChar() { text = text.Replace("/n", "/r/n"); return text; } } //执行对象。需要采用可替换策略执行的对象 public class ContextCharChange { //执行对象依赖于策略对象的操作方法 public static String contextInterface(TextStrategy strategy) { return strategy.replaceChar(); } } } // end of namespace

 

 

 

Strategy Pattern 的优点:

  • 简化了单元测试,因为每个算法都有自己的类,可以通过自己的接口单独做测试。
  • 避免程序中使用多重条件转移语句,使系统更灵活,并易于扩展。
  • 高内聚、低偶合。


Strategy Pattern 的缺点:

  • 因为每个具体策略都会产生一个新类,所以会增加需要维护的类的数量。
  • 选择所用具体实现的职责由客户程序承担,并转给 Context 对象,并没有解除客户端需要选择判断的压力。


若要减轻客户端压力,或程序有特殊考量,还可把 Strategy 与 Simple Factory 两种 Pattern 结合,即可将选择具体算法的职责改由 Context 来承担,亦即将具体的算法,和客户程序做出隔离。

你可能感兴趣的:(Strategy Pattern (策略模式))