艾伟_转载:C# Design Patterns (2) - Strategy

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 计算机上,因硬件等级不同,必须采用不同的排序算法。
  • 针对给定的目的,存在多种不同的算法,且我们可用代码实现算法选择的标准。
  • 需要封装复杂的数据结构。比如:特殊的加密算法,客户程序仅需要知道调用的方式即可。
  • 同上,算法中的罗辑和使用的数据,应该与客户程序隔离时。

 


图 1 这张为很多书籍和文档都曾出现过的 Strategy 经典 Class Diagram

 

01_Shell.aspx.cs
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() + "
");

        context = new Context(new ConcreteStrategyB());
        Response.Write(context.ContextInterface() + "
");

        context = new Context(new ConcreteStrategyC());
        Response.Write(context.ContextInterface() + "
");
    }
}

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 文件夹中的类,或其他类库项目中的类),使他们易于组合、更换,便于日后的维护和修改。


图 2 示例 02_Strategy.aspx.cs 的 Class Diagram。此为 Sybase PowerDesigner 的「Reverse Engineer」功能,所自动产生的图

 

02_Strategy.aspx.cs
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

 

 


图 3 示例 02_Strategy.aspx.cs 的执行结果

 

若未用任何 Pattern 的客户程序,可能就如下方的硬编码,将「换行符」和算法,直接写死在 ASP.NET 的 Code-Behind 里,导至事后的维护和扩展不易。 

hard coding
protected void DropDownList1_SelectedIndexChanged(object sender, EventArgs e)
{
    switch(DropDownList1.SelectedValue)
    {
        case "Linux":
            Label1.Text = strWindowsText.Replace("\r\n", "\n");
            break;
        case "Windows":
            Label1.Text = strLinuxText.Replace("\n", "\r\n");
            break;
        default:
            Label1.Text = String.Empty;
            break;
    }
}

 

此外,若用 Simple Factory Pattern (简单工厂模式) 虽然也能解决上述硬编码的问题,但就如我们前一篇帖子「C# Design Patterns (1) - Factory Method」曾经提过的缺点,日后若要添加或修改功能时,仍要修改、重新编译 server-side 的「工厂类」。所以在此种情况下,用 Strategy 会是比 Simple Factory 更好的选择。

--------------------------------------------------------

Strategy Pattern 的优点:

 

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

Strategy Pattern 的缺点:

 

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

若要减轻客户端压力,或程序有特殊考量,还可把 Strategy 与 Simple Factory 两种 Pattern 结合,即可将选择具体算法的职责改由 Context 来承担,亦即将具体的算法,和客户程序做出隔离。有关这方面的概念和示例,可参考伍迷的「大话设计模式」一书 [10]。

--------------------------------------------------------

此外,从行为上来看,State Pattern 和 Strategy Pattern 有点类似,但前者可看作后者的动态版本。

  • State:看当前是什么状态,就采取什么动作。
  • Strategy:看需求及情景为何,采用适当的策略。

State 中,当对象内部的状态改变时,它可切换到一组不同的操作,从而改变对象的行为,例如 GoF 示例中的 TCP 连接;而 Strategy 是直接采用适当的策略 (算法),如本帖示例中,不同的操作系统,实现换行的具体算法类 LinuxStrategy 与 WindowsStrategy。

时间: 2024-07-30 04:33:02

艾伟_转载:C# Design Patterns (2) - Strategy的相关文章

艾伟_转载:C# Design Patterns (4) - Proxy

本帖介绍 Proxy Pattern (代理模式). Proxy Pattern (代理模式) The Proxy Pattern provides a surrogate or placeholder for another object to control access to it...                                  - Design Patterns: Elements of Reusable Object-Oriented Software 在 Go

艾伟_转载:C# Design Patterns (3) - Decorator

Decorator Pattern (装饰模式) 装饰模式可「动态」地给一个对象添加一些额外的职责,提供有别于「继承」的另一种选择.就扩展功能而言,Decorator Pattern 透过 Aggregation (聚合) 的特殊应用,降低了类与类之间的耦合度,会比单独使用「继承」生成子类更为灵活. 一般用「继承」来设计子类的做法,会让程序变得较僵硬,其对象的行为,是在「编译」时期就已经「静态」决定的,而且所有的子类,都会继承到相同的行为:然而,若用「装饰模式」以及 UML 的 Aggregat

艾伟_转载:C# Design Patterns (5) - Prototype

本帖介绍 Prototype Pattern (原型模式),并以一个「人事招聘程序」作为示例来说明. --------------------------------------------------------本帖的示例下载点:http://files.cnblogs.com/WizardWu/090713.zip第一个示例为 Console Mode (控制台应用程序) 项目,第二个示例为 ASP.NET 网站项目.执行示例需要 Visual Studio 2008 或 IIS + .NE

艾伟_转载:C# Design Patterns (1) - Factory Method

Simple Factory Pattern (简单工厂模式) 特性: 把类的实例化工作,集中到一个「工厂类」去处理,亦即将 new instance 的工作,都交给一个「工厂」去处理,而不要分散写在各个类中. 客户端程序,与创建实例 (对象) 的工作必须隔离,亦即「解耦」,客户端程序只要专注于自己的业务逻辑.适用于客户端程序在开发过程中,尚无法预知要创建的具体类型. 产品具体的实现能和客户端隔离,便于事后抽换. Simple Factory Pattern (简单工厂模式).Factory M

艾伟:C# Design Patterns (2) - Strategy

Strategy Pattern (策略模式) 所谓 Strategy Pattern 的精神,就是将策略 (算法) 封装为一个对象,易于相互替换,如同 USB 设备一样可即插即用:而不是将策略.具体的算法和行为,硬编码在某个类或客户程序中,导至事后的修改和扩展不易. 若有多种「策略」,就将这些个策略,和这些策略的算法.行为,封装在各个类中,并让这些类,去继承某个公用的抽象类或接口.接着在客户程序中,就可动态引用,且易于更换这些不同的「策略」,不会因为日后添加.修改了某一个「策略」,就得重新修改

艾伟_转载:虚方法的使用

<编程絮语>之一 C#的语法脱胎于C++,因而保留了virtual关键字,可以定义一个虚方法(或虚属性).一个类的成员被定义为virtual,就意味着它在告诉自己的子类:我准备了一笔遗产,你可以全盘接受,也可以完全拒绝或者修改我的遗嘱.显然,虚方法授予子类的权利甚至大于抽象方法.子类面对抽象方法只有重写(override)的权利,而对于虚方法,它还可以选择完全继承. 毫无疑问,虚方法破坏了对象的封装性.如果不加约束的使用,会对调用方造成破坏,至少它有可能破坏子类与父类之间在外在行为上的一致性.

C# Design Patterns (2) - Strategy

Strategy Pattern (策略模式) 所谓 Strategy Pattern 的精神,就是将策略 (算法) 封装为一个对象,易于相互替换,如同 USB 设备一样可即插即用:而不是将策略.具体的算法和行为,硬编码在某个类或客户程序中,导至事后的修改和扩展不易. 若有多种「策略」,就将这些个策略,和这些策略的算法.行为,封装在各个类中,并让这些类,去继承某个公用的抽象类或接口.接着在客户程序中,就可动态引用,且易于更换这些不同的「策略」,不会因为日后添加.修改了某一个「策略」,就得重新修改

艾伟_转载:.NET设计模式:单件模式(Singleton Pattern)

概述 Singleton模式要求一个类有且仅有一个实例,并且提供了一个全局的访问点.这就提出了一个问题:如何绕过常规的构造器,提供一种机制来保证一个类只有一个实例?客户程序在调用某一个类时,它是不会考虑这个类是否只能有一个实例等问题的,所以,这应该是类设计者的责任,而不是类使用者的责任. 从另一个角度来说,Singleton模式其实也是一种职责型模式.因为我们创建了一个对象,这个对象扮演了独一无二的角色,在这个单独的对象实例中,它集中了它所属类的所有权力,同时它也肩负了行使这种权力的职责! 意图

艾伟_转载:WCF版的PetShop之三:实现分布式的Membership和上下文传递

本系列文章导航 WCF版的PetShop之一:PetShop简介 WCF版的PetShop之二:模块中的层次划分 WCF版的PetShop之三:实现分布式的Membership和上下文传递 通过上一篇了解了模块内基本的层次划分之后,接下来我们来聊聊PetShop中一些基本基础功能的实现,以及一些设计.架构上的应用如何同WCF进行集成.本篇讨论两个问题:实现分布式的Membership和客户端到服务端上下文(Context)的传递. 一. 如何实现用户验证 对登录用户的验证是大部分应用所必需的,对