解读设计模式----策略模式(Strategy Pattern)

一、模式概述
      策略模式(Strategy Pattern)在外形上与状态模式很相似,但在意图上有些不同。其意图是使这些算法可以相互替换,并提供一种方法来选择最合适的算法。
      在我应用OOP的设计过程演化(三)这篇文章里应用到了策略模式,在图书的租金计算上分多种情况,每一种不同类型的图书的租金是不一样的,而站在用户的角度来看,不同类型用户的租金收取又是不一样的,见下面分析:
      计算机类图书:会员租借打5折,普通顾客租借打6折。
      小说类图书:会员租借打6折,普通顾客租借打8折。
      生活类图书:会员租借打9折,普通顾客租借打9折。
     
      从上面分析可知,在租金计算这块是非常复杂的,由于有这样复杂的折扣算法,使得价格计算问题需要系统地解决。
     书店经营一段时间后发现生活类的图书出租量很小,或许是因为租金的原因,很少有人来租借,于是老板决定降低租金,将生活类图书的租金按照计算机类的租金价格收取。这个时候怎么办?是不是已经有现存的计算机类图书租金计算策略,此时我们只需要把生活来图书的租金计算策略换为计算机类图书的租金计算策略便OK。通过这样的分析,策略模式(Strategy)正是解决这样的问题的模式,它的定义:"准备一组算法,并将每一个算法封装起来,使得它们可以互换。"

二、模式定义
     定义:"准备一组算法,并将每一个算法封装起来,使得它们可以互换。"

三、模式意图
      策略模式的用意是针对一组算法,将每一个算法封装到具有共同接口的独立的类中,从而使得它们可以相互替换。

四、模式UML图
                        
五、模式参与者
     根据上面UML,不难看出,在策略模式里主要有三种角色:环境角色、抽象策略角色和具体策略角色。
  环境(Context)角色:持有一个抽象策略(Strategy)角色的引用。也叫上下文。
  抽象策略(Strategy)角色:这是一个抽象角色,通常由一个接口或一个抽象类来实现。
  具体策略(ConcreteStrategy)角色:包装了相应的算法和行为。

六、简单示例
      比如我们有这样一个需求,当我们的系统在记录操作日志的时候,客户要求提供多种日志记录策略,通过设置可以将日志记录到不同的地方(文本文件、XML文件以及数据库等),那么根据策略模式的定义,我们完全可以把不同的日志记录算法定义为一个独立的策略对象。
 1namespace DesignPattern.Strategy
 2{
 3    /// <summary>
 4    /// 抽象策略角色:这是一个抽象角色,通常由一个接口或抽象类实现。
 5    /// 此角色给出所有的具体策略类所需的接口。
 6    /// </summary>

 7    public abstract class Strategy
 8    {
 9        public abstract void WriteLog();
10    }

11}

 1/// <summary>
 2/// 具体策略角色:包装了相关的算法或行为。
 3/// </summary>

 4public class DBStrategy:Strategy
 5{
 6    public override void WriteLog()
 7    {
 8        Console.WriteLine("日志被记录到数据库!");
 9    }

10}

11
12/// <summary>
13/// 具体策略角色:包装了相关的算法或行为。
14/// </summary>

15public class TXTStrategy:Strategy
16{
17    public override void WriteLog()
18    {
19        Console.WriteLine("日志被记录到TXT文件!");
20    }

21}

22
23/// <summary>
24/// 具体策略角色:包装了相关的算法或行为。
25/// </summary>

26public class XMLStrategy:Strategy
27{
28    public override void WriteLog()
29    {
30        Console.WriteLine("日志被记录到XML文件!");
31    }

32}

 1namespace DesignPattern.Strategy
 2{
 3    /// <summary>
 4    /// 环境角色:持有一个Strategy类的引用
 5    /// </summary>

 6    public class Context
 7    {
 8        Strategy strategy;
 9        /// <summary>
10        /// 初始化时传入具体的策略对象
11        /// </summary>
12        /// <param name="strategy"></param>

13        public Context(Strategy strategy)
14        {
15            this.strategy = strategy;
16        }

17
18        public void ContextStrategy()
19        {
20            strategy.WriteLog();
21        }

22
23        /// <summary>
24        /// 动态设置策略
25        /// </summary>
26        /// <param name="strategy"></param>

27        public void SetStrategy(Strategy strategy)
28        {
29            this.strategy = strategy;
30        }

31    }

32}

      定义了一个抽象策略角色(Strategy),三种不同的日志记录策略(TXTStrategy、XMLStrategy和DBStrategy),也就是模式参与者中的具体策略角色,在环境角色里持有一抽象策略角色的引用,并通过构造器传入具体的策略对象初始化策略角色的引用,我们还定义了一动态设置策略的方法(SetStrategy),UML如下:
                        
      那么客户端就可以这样来使用这个策略:
 1namespace DesignPattern.Strategy
 2{
 3    class Program
 4    {
 5        static void Main(string[] args)
 6        {
 7            //初始化时传入具体的策略对象
 8            Context context = new Context(new XMLStrategy());
 9            context.ContextStrategy();
10
11            //动态设置策略对象--改变策略
12            context.SetStrategy(new DBStrategy());
13            context.ContextStrategy();
14
15            context.SetStrategy(new TXTStrategy());
16            context.ContextStrategy();
17        }

18    }

19}

      策略模式是一个提倡“针对接口编程”的模式,而使用接口的目的是为了统一标准或着说是制定一种强行的规定。此处抽象策略角色就担任了制定规定的角色,它制定了一种写日志(WriteLog方法)的规定。只要系统中需要记录日志就得使用这个规定(调用WriteLog方法),直接就使用他是不行的,就如去商场购物,要把商品带走不付费怎么行?

      策略模式的使用是由用户发起的,根据用户的操作决定使用什么具体的策略角色。策略模式仅仅封装算法,提供新的算法加入到已有系统和算法间的相互替换,以及老算法从系统中“退休”的方便。策略模式并不决定在何时使用何种算法。因为使用何种算法决定权在用户,也就是说我们需要调用策略模式中定义好的算法方法之前,必须得选择一种具体的策略算法,上例中,客户端需要调用ContextStrategy方法,则调用前得为其设置一个具体的策略算法,如下:
1//初始化时传入具体的策略对象
2Context context = new Context(new XMLStrategy());
3context.ContextStrategy();

七、PetShop 4.0中的策略模式
     PetShop 4.0的体系结构非常庞大,在订单处理上设计了两种处理策略,这里也是策略模式的一个应用,IOrderStrategy接口作为订单策略的高层抽象(抽象策略角色),实现不同订单处理的具体策略去实现它,UML如下:
                         

示意性代码:
1namespace PetShop.IBLLStrategy
2{
3    public interface IOrderStrategy
4    {
5        void Insert(OrderInfo order);
6    }

7}

 1namespace PetShop.BLL
 2{
 3    public class OrderSynchronous:IOrderStrategy
 4    {
 5        private static readonly IOrder asynchOrder = QueueAccess.CreateOrder();
 6
 7        public void Insert(OrderInfo order)
 8        {
 9            asynchOrder.Send(order);
10        }

11    }

12}

13namespace PetShop.BLL
14{
15    public class OrderAsynchronous:IOrderStrategy
16    {
17       //
18    }

19}

      从上面UML和代码就可以看出,订单策略接口下有两种实现,使用了抽象工厂模式来完成相应的订单策略对象的创建 。关于定单策略对象的创建详细可查阅ASP.NET中进行消息处理(MSMQ) 三 这篇文章,我在这里作了详细介绍。

八、模式的相关优点及缺点
   定义了一系列的可供重用的算法和行为,继承有助于析取出这些算法中的公共功能。
   Strategy模式可以提供相同行为的不同实现,客户可以根据不同的需求选择不同的Strategy。
   客户必须了解不同的Strategy,一个客户要选择一个合适的Strategy就必须知道这些Strategy到底有何不同。
   Strategy模式会增加对象的数目(具体的策略对象),可以将Strategy实现为可供各Context共享的无状态的对象来减少这一开销。任何其与的状态都由Context维护。

九、参考资料
    张逸 《软件设计精要与模式》
    GOF 《设计模式--可复用面向对象软件的基础》
    MS开源项目PetShop 4.0

注:原创文章,转载请注明出处:http://beniao.cnblogs.com  或 http://www.cnblogs.com   作者:beniao
posted on 2008-07-08 14:24  Bēniaǒ  阅读(3926)  评论(15编辑  收藏  举报