设想我们要开发一个鸭子游戏,我们设计了各种鸭子,鸭子会游泳,会叫,还有各种自己的外观。按照面对对象的设计,我们很
容易实现这个游戏,首先我们声明一个鸭子的基类 Duck,这个鸭子类如下:
public class Duck
{
public void Swim()
{ }
public void Quack()
{ }
public virtual void Display()
{ }
}
然后我们可以声明各种鸭子继承这个类,比如绿头鸭,红头鸭之类的。 它们都会叫,会游泳,但是有不同的外观,所以我们什么
Display为virtual,每个子类将自己实现它。
看上去很完美,我们遵循了面对对象的原则。但是有一天,市场人员告诉我们,鸭子要有更多的特性,有的鸭子要会飞,有点鸭
子则不能。第一感觉,很简单,在基类Duck中加上一个virtual方法Fly(),如果不需要鸭子会飞,那么我们在子类重载,把Fly设定
为不会飞。功能是实现了,但是我们会感到不爽,也许我们有30种鸭子不会飞,那么我们要在30个子类中做这件事,于是出现了
重复代码。于是第二选择,我们声明一个接口IFlyable,只有需要飞的鸭子,才需要实现这个接口。接口 仍然没有解决代码重用的
问题,我们需要给每个会飞的鸭子重写Fly方法。问题似乎麻烦了,而且很有可能有一天,市场人员会告诉我们鸭子飞行的姿势会
不一样,绿头鸭朝南飞,黑头鸭红头鸭朝北飞,我们需要提前考虑市场人员的需求,怎么办?软件开发中,亘古不变的永远是变
化,还记得我们的软件设计原则不? 封装变化,把需求中需要变化的提取出来,不要和不变的部分混合在一起。这样我们改变变
化的部分的时候,不会影响到不变的部分。提取变化并封装变化,参照软件设计原则,针对接口编程,而不是针对实现编程,所
以我们重新实现Duck,并声明接口IFlyable,实现接口,代码如下:
1. 声明接口:
public interface IFlyable
{
void Fly();
}
2. 声明基类
public abstract class Duck
{
public IFlyable FlyBehavior { set; get; }
public void Swim()
{ }
public void Quack()
{ }
public virtual void Display()
{ }
public void Fly()
{
FlyBehavior.Fly();
}
}
我们可以看到,在基类成了一个抽象类,多了一个接口对象 FlyBehavior ,在Fly方法中,我们调用了FlyBehavior的Fly方法。到此
为止,我们把Fly的方法彻底下放给接口的实现了。同时我们是不是也遵循了另外一个设计原则:尽量用have-a而不是is-a(多用组
合,少用继承).
3. 接口的实现
public class FlyTowardSouth : IFlyable
{
public void Fly()
{
Console.WriteLine("Fly toward south");
}
}
public class FlyTowardNorth : IFlyable
{
public void Fly()
{
Console.WriteLine("Fly toward North");
}
}
public class FlyNoWay : IFlyable
{
public void Fly()
{
Console.WriteLine("Fly No way");
}
}
我们实现了向南飞,向北飞,不会飞,这样具体什么鸭子往哪边飞,我们只要不同的接口实现对象即可。
4. 真实的鸭子
public class MallardDuck : Duck
{
public MallardDuck()
{
this.FlyBehavior = new FlyTowardNorth();
}
}
看到没有,这只鸭子会向北飞,如果我们要它向南飞,只需要 this.FlyBehavior = new FlyTowardSouth();即可。
如果有一天,市场人员要我们开发向西飞的鸭子,我们只要实例化一个实现了向西飞的接口即可。
这就是策略模式,使用策略模式,我们是不是实现了代码的重用,提高了软件的灵活性?
最后是策略模式的定义:策略模式抽取算法,并分别封装起来,让他们之间可以互相替换,此模式让算法的变化独立于使用算法的客户。