Strategy Pattern - 策略模式

一、概念

Strategy Pattern - 策略模式,定义了一些列的算法,并将每个系列封装起来,这使得它们可以互换。策略让我们可以独立于使用它的客户端来改变算法。

二、从示例中学习

假设我们在某厂台灯厂工作,该厂生产各式各样的台灯。现在厂里要求做个应用程序向用户展示各类台灯的工作方式。所以我们又不能偷懒了:(,工作开始……

首先我们分析了下,每个台灯(Lamp)都有各自的样式(Display);一个控制按钮(Pushbutton),按下按钮灯便开始发光。所以我们设计了个Lamp基类,它有两个方法display()与push()。

    public abstract class Lamp
    {
        
//每种台灯都有按钮
        public void Push()
        {
            Console.WriteLine(
"I'm shining now!");
        }

        
//样式因lamp而异,所以Display()设置方法抽象
        public abstract void Dsplay();
    }

接着A灯、B灯、...都继承了基类Lamp,同时各自改写Display(),这样它们便拥有了与众不同的样式。

    class Alamp : Lamp
    
{
        
public override void Display()
        
{
            Console.WriteLine(
"Alamp-style is shown");
        }

    }


    
class Blamp : Lamp
    
{
        
public override void Display()
        
{
            Console.WriteLine(
"Blamp-style is shown");
        }

    }

    
//

大功告成!

可是一年后,我们厂意识到在台灯下工作的人都是多么的重视时间,所以将所生产的所有台灯都装上了一个小时钟。为了节约成本,部门领导要求我们不用重写展示程序了,只要相应的添加个时钟(Timer)就可以了。嘿嘿,多么轻松的工作,我们只要在基类Lamp里加个Timer()方法,这样所有的lamp因为继承了Lamp便都有了个Timer()。

        public void Timer()
        {
            Console.WriteLine(
"Show time.");
        }

可是不幸的是,厂子的这个决策导致上半年的业绩狂跌。原因是,并不是所有人都喜欢有个时钟在台灯上!于是领导层又发出指示:有时钟与无时钟品种按3/7生产。那么我们当然又要——

Tips我们认为用继承来实现代码重用是很棒的方法,然而我们在维护程序的时候碰到了麻烦。

那么接下来我们是否想到,既然Display因灯而异,我们把它设置为抽象方法,然后再实现中重写,那么我们的Timer不是也可以这样,辛好灯的类型不多,我们在全部的lamp中加个Timer的重写代码?!继续思考,要是我们有些灯又加了个日历表呢?如果采用Timer这样的处理方法,系统便没有了代码重用这回事了,不是吗?而且重写的工作劳苦啊!有加班费就无所谓了:)

于是我们想到了采用接口,将可有可无的功能设置为接口,如Timer、Calendar;而将一定有的特性放在基类中,如Display。代码如下:

Hunts.Pattern.Strategy_Pattern
Tips在软件开发中我们碰到最多的事是:变化。我们在想是否有种方法可以在我们需要改变原有程序时,尽量的不影响原有的代码,花较少的时间在代码的修改上,而把时间放在让程序实现更加精彩的功能上呢?
软件设计原则识别应用程序中变化的方面,并将它们从不变化的那部分分离出来。

将变化的部分分离并封装起来,那么这块代码便不会影响其它的代码了,这样可以减少改变代码时出现的意外结果,同时也使我们的系统更加灵活。

 那么我们回过头再思考下采用接口后的程序。由于Timer和Calendar都是变化的,我们把它们重基类Lamp中分离出来了。我们还可以做哪些工作呢?

设计原则将分离出来的部分设计成接口,而不是实现。

我们可以定义两个接口类:
    //ITimerBehavior接口
    public interface ITimerBehavior
    
{
        
void Showtime();
    }


    
//ICalendarBehavior接口
    public interface ICalendarBehavior
    
{
        
void Showdate();
    }

接着,我们将具体的行为放入实现了特定行为接口的类中,这样各种lamp类就不用知道他们自己到底有什么behaviors(时钟或日历):

//将具体的行为放入实现了特定行为接口的类中
    class TimerExist : ITimerBehavior
    
{
        
void ShowTime()
        
{
            Console.WriteLine(
"With a timer.");
        }

    }

    
class TimerNonexist : ITimerBehavior
    
{
        
void ShowTime()
        
{
            Console.WriteLine(
"Without a timer.");
        }

    }


    
class CalendarExist : ICalendarBehavior
    
{
        
void ShowDate()
        
{
            Console.WriteLine(
"With a Calendar.");
        }

    }

    
class CalendarNonexist : ICalendarBehavior
    
{
        
void ShowDate()
        
{
            Console.WriteLine(
"With a Calendar.");
        }

    }
Tips将变化的部分编写成一个接口(interface),实际上是编写了一个父型(supertype)。

先举个例子(来自书中)。比如有个抽象类Animal,带有两个具体的实现,Dog和Cat。
//Programming to an implementation
Dog d = new Dog();
d.bark();

//Programming to an interface.supertype
Animal animal = new Dog();
animal.makeSound();

//assign the concrete implementation object at runtime
= getAnimal();
a.makeSound();

如上所示,我们如果编写接口的话可以让我们多态的引用animal,甚至可以在运行时分配给animal一个具体的实现。即使在不知道a是什么动物的情况下,我们也知道如何响应makeSound()。

 回到例子前面的代码,这样的设计使得其它的对象也可以使用ShowTime和ShowDate行为,因为这些行为不再被lamp类独占。这样也就得到了Reuse的好处。而且我们可以增加各种behavior而不改变任何已存在的behavior,或影响到使用ShowTme或ShowDate的lamp类。

集成Lamp的Behavior

接下来我要集成Lamp的Behavior,关键点是现在我们的Lamp基类将委派ShowTime和ShowDate行为。

  1. 首先添加两个引用变量:
    •         //声明两个行为接口类型的变量,注意要设置为public,因为所有的子类需要继承这些
              public ITimerBehavior timerBehavior;
              
      public ICalendarBehavior calendarBehavior;
  2. 实现performShowTime()方法:
    •         public void performShowTime()
              {
                  
      //委派(delegate)行为类
                  timerBehavior.ShowTime();
              }

              
      public void performShowDate()
              {
                  calendarBehavior.ShowDate();
              }
  3. 实现要生产的台灯类:
    •     public class Alamp : Lamp
          
      {
              
      public Alamp()
              
      {
                  
      //带有时钟与台历
                  timerBehavior = new TimerExist();
                  calendarBehavior 
      = new CalendarExist();
              }


              
      public override void Display()
              
      {
                  Console.WriteLine(
      "Alamp-style is shown.");
              }
              
          }


          
      public class Blamp : Lamp
          
      {
              
      public Blamp()
              
      {
                  
      //无时钟,有台历
                  timerBehavior = new TimerNonexist();
                  calendarBehavior 
      = new CalendarExist();
              }


              
      public override void Display()
              
      {
                  Console.WriteLine(
      "Blamp-style is shown.");
              }

          }


          
      public class Clamp : Lamp
          
      {
              
      public Clamp()
              
      {
                  
      //有时钟,无台历
                  timerBehavior = new TimerExist();
                  calendarBehavior 
      = new CalendarNonexist();
              }


              
      public override void Display()
              
      {
                  Console.WriteLine(
      "Clamp-style is shown.");
              }

          }
  4. 有客户购买了台灯并使用:
    • //测试
          class Program
          
      {
              
      static void Main(string[] args)
              
      {
                  Lamp alamp 
      = new Alamp();
                  alamp.Display();
                  alamp.Push();
                  alamp.performShowTime();
                  alamp.performShowDate();

                  Console.WriteLine(
      "/****************/");

                  Lamp blamp 
      = new Blamp();
                  blamp.Display();
                  blamp.Push();
                  blamp.performShowTime();
                  blamp.performShowDate();

                  Console.WriteLine(
      "/****************/");

                  Lamp clamp 
      = new Clamp();
                  clamp.Display();
                  clamp.Push();
                  clamp.performShowTime();
                  clamp.performShowDate();

                  Console.ReadLine();
              }

          }

  5. 完整代码:Design_Pattern(.rar)

三、面向对象设计

    1. OO要素:
      1. 抽象
      2. 封装
      3. 多态
      4. 继承
    2. OO原则:
      1. 封装变化点
      2. 优先使用对象组合,而不是类继承
      3. 针对接口编程,而不是针对实现编程

四、更多资源

    1. MSDN WebCast 李建忠老师的:C#面向对象设计模式纵横谈
    2. 吕震宇老师的: 设计模式(22)-Strategy Pattern

本篇内容仅为个人学习笔记,多数内容参考自书中。仅作参考。

本Blog中所有内容皆以“现状”提供且没有任何担保,同时也没有授予任何权利。
This posting is provided "AS IS" with no warranties, and confers no rights.

posted on 2007-01-06 23:45  Hunts.C  阅读(494)  评论(2)    收藏  举报