C#设计模式-【HeadFirst设计模式学习笔记】重学策略模式(Strategy Pattern)
从意义上看,策略模式提供了这样一种便利:
当一系列对象包含多种行为,并且这些行为会有不同的算法实现的时候,策略模式就是一个易于配置的应对方法。
在HeadFirst设计模式中,第一章先让我们无意中实现了这一模式:) 不得不感叹作者的苦心。我们设想如下的一个用例:
公司在开发一个鸭子游戏,游戏当中有几种不同的鸭子,鸭子将可以执行鸣叫,飞行等行为。我们如何使用面向对象的方法来实现它?
首先想到的是实现一个鸭子的基类,包含鸣叫和飞行的virtual方法,并定义一系列外观相关的属性,如头冠的颜色,羽毛的颜色等。不同的鸭子将继承这个基类,并根据需要实现对应不同的鸣叫和飞行的方法。
public class DuckBase
{
#region Fields
protected Nullable<DuckColors> _color = null;
protected Nullable<DuckHatColors> _hatColor = null;
#endregion
#region Properties
public Nullable<DuckColors> Color
{
get { return this._color; }
set { this._color = value; }
}
public Nullable<DuckHatColors> HatColor
{
get { return this._hatColor; }
set { this._hatColor = value; }
}
#endregion#region Methods
public virtual string DoQuack()
{
return DuckQuackTexts.FLY_COMMON ;
}
public virtual string DoFly()
{
return DuckFlyTexts.FLY_COMMON;
}
#endregion
}
鸭子的子类:
public sealed class FlyingDuck : DuckBase
{
public FlyingDuck()
{
this._color = DuckColors.WHITE;
this._hatColor = DuckHatColors.RED;
}
public override string DoFly()
{
return base.DoFly();
}
}
但是不久之后问题出现了,有的鸭子会飞而有的不会,当越来越多种类的鸭子出现时,覆盖基类的飞行和鸣叫的方法将成为无尽的噩梦,也可能因为错误的覆盖而出现“会飞的橡皮鸭子”。于是有了一个新的重构方法:使用接口来代替继承。实现类似IFlyable,IUnFlyable的接口,并根据不同鸭子的种类来实现不同的接口。
实现“会飞”,“不会叫”接口的鸭子子类:
public sealed class FlyingDuck : DuckBase, IFlyable, IUnQuackable
{
public FlyingDuck()
{
this._color = DuckColors.WHITE;
this._hatColor = DuckHatColors.RED;
}
public string DoFly() //Implement the DoFly from IFlyable
{
return DuckFlyTexts.FLY_COMMON ;
}
}
虽然规范化了鸭子的行为,而这种方法带来了更多的问题:首先重复代码量大大增加了,每个鸭子都需要实现具体的飞行和鸣叫方法;另外鸭子的飞行也可能产生变化。因为需求当中说,我们的鸭子将可以装备火箭发射器,具备高速飞行能力。。使用接口使得这种实现变得相当艰难。
于是解决问题的模式来了:使用策略模式。我们首先创建两个Behavior接口,分别包含飞行和鸣叫的定义。为每个鸭子添加两个Behavior属性,分别对应这两个接口。同时,每当出现一个新的行为时,我们只需要编写一个实现了Behavior接口的类来定义具体的行为,然后将此行为设置到需要它的鸭子的对应Behavior属性。
飞行的行为接口:
public interface IFly
{
string DoFly();
}
一个实现飞行行为接口的行为:
public sealed class CommonFlyBehavior : IFly
{
#region IFly Members
public string DoFly()
{
return DuckFlyTexts.FLY_COMMON;
}
#endregion
}
重写鸭子的父类,这里在SetBehavior,也就是设置鸭子行为类型的方法中,我使用了泛型方法并编写了对应的约束来简化行为类的构造操作。当然为的是我们在子类中不需要些类似this._flyBehavior = new CommonFlyBehavior(); 这样的劳什子:
public class DuckBase : IFly, IQuack
{
#region Fields
protected Nullable<DuckColors> _color = null;
protected Nullable<DuckHatColors> _hatColor = null;
protected IFly _flyBehavior = null;
protected IQuack _quackBehavior = null;
#endregion
#region Properties
public Nullable<DuckColors> Color
{
get { return this._color; }
set { this._color = value; }
}
public Nullable<DuckHatColors> HatColor
{
get { return this._hatColor; }
set { this._hatColor = value; }
}
#endregion
protected void SetBehavior<TFlyBehavior,TQuackBehavior>()
where TFlyBehavior:IFly, new()
where TQuackBehavior : IQuack,new()
{
this._flyBehavior = new TFlyBehavior();
this._quackBehavior = new TQuackBehavior();
}
#region IQuackBehavior Members
public virtual string DoQuack()
{
return this._quackBehavior.DoQuack();
}
#endregion
#region IFlyBehavior Members
public virtual string DoFly()
{
return this._flyBehavior.DoFly();
}
#endregion
}
然后在某个鸭子子类当中,我们只需要这样写:
public sealed class FlyingDuck : DuckBase
{
public FlyingDuck()
{
this._color = DuckColors.WHITE;
this._hatColor = DuckHatColors.RED;
this.SetBehavior<CommonFlyBehavior,CommonQuackBehavior>();
}
}
仅仅需要一个SetBehavior的方法来设置对应的行为。这样就也解决了行为变化的问题,因为我们很容易在代码中随时为鸭子来替换它的行为。
创建新的RocketFlyBehavior行为类:
public sealed class RocketFlyBehavior : IFly
{
#region IFly Members
public string DoFly()
{
return DuckFlyTexts.FLY_HIGH;
}
#endregion
}
创建新的LoudQuackBehavior行为类:
public sealed class LoudQuackBehavior : IQuack
{
#region IQuack Members
public string DoQuack()
{
return DuckQuackTexts.SPEAKING;
}
#endregion
}
覆盖鸭子子类的DoFly方法:
public override string DoFly()
{
StringBuilder sb = new StringBuilder();
sb.Append(base.DoFly());
// 这里改变了飞行的行为
this.SetFlyBehavior<RocketFlyBehavior, LoudQuackBehavior>();
sb.Append(this._flyBehavior.DoFly());
return sb.ToString();
}
这样,当以后出现新的行为时,我们只需要创建对应实现IFly的Behavior类,并改变对应鸭子的行为设置即可。
总结了下值得注意的设计原则:
1. 尽可能使用组合而不是继承
2. 使用变化的思维来构造我们的程序,所有东西都可能发生变化
3. 可能发生变化的部分,我们可以考虑将其抽象化并独立出来,组合到需要它的对象中。