缘起
每周一篇的计划刚定下就迎来了第一周,那我该写些什么呢?思考了一下,刚好最近在看Head First 设计模式一书,那么我就来说说设计模式吧。虽然关于设计模式的博客,博客园里已经有很多大佬写过了,我就权当加深自己的印象和理解。
开始
首先,我们来提一个需求。我们拥有一款鸭子模拟游戏,现在我们有两种鸭子类型,一种绿头鸭(MallardDuck),一种红头鸭(RedheadDuck),它们都拥有外貌描述(dispaly)、叫(quack)和飞(fly)的方法。
那么代码会是这样的
public abstract class Duck
{
public abstract void Display();
public void Quack()
{
Console.WriteLine("呱呱");
}
public void Fly()
{
Console.WriteLine("飞行");
}
}
public class MallardDuck:Duck
{
public override void Display()
{
Console.WriteLine("我是绿头的");
}
}
public class RedheadDuck:Duck
{
public override void Display()
{
Console.WriteLine("我是红头的");
}
}
这样已经满足了我们的需求,但这时产品又提出了一个需求,我想要增加一种新的鸭子类型-橡皮鸭(RubberDuck)。这简单,我们这样做:
public class RubberDuck:Duck
{
public override void Display()
{
Console.WriteLine("我是橡皮鸭的");
}
}
是不是看出来哪里不对了。是的,橡皮鸭怎么会飞呢,而且它也不是“呱呱”的叫。当需求改变时,为了复用之前的代码,我们用了继承,结局并不完美。
当涉及“维护”时,为了“复用”(reuse)目的而使用继承,结局并不完美。
如何解决呢?也很简单。我们重写Duck的Fly方法和Quack方法,并且Fly方法什么也不干。
public class RubberDuck:Duck
{
public override void Display()
{
Console.WriteLine("我是橡皮鸭的");
}
public override void Quack()
{
Console.WriteLine("嘎嘎");
}
public override void Fly()
{
}
}
可是这样就完美了吗?每当有新的鸭子类出现,我们就要被迫检查并可能需要覆盖Fly()和Quack()方法。那我们将Fly()从超类中提取出来,放进一个IFlyable接口,只有会飞的鸭子才实现此接口,这种解决方法让我们必须去实现接口的方法,代码无法复用,只是从一个噩梦跳入另一个噩梦。那么这个问题是不是无解了?
刚好我们最近看了Head First设计模式,看看有没有适合我们使用的方式。
找出应用中可能需要变化之处,把它们独立出来,不要和那些不需要变化的代码混在一起。
Duck类仍是超类,但我们将会变化的部分,也就是Fly和Quack取出。
接着我们设计鸭子的行为
针对接口(超类)编程,而不是针对实现编程。
根据设计原则,我们将这样设计
public interface IFlyBehavior
{
void Fly();
}
public class FlyNoWay : IFlyBehavior
{
public void Fly()
{
Console.WriteLine("不会飞");
}
}
public class FlyWithWings : IFlyBehavior
{
public void Fly()
{
Console.WriteLine("飞行");
}
}
将Fly行为定义为接口,并让FlyNoWay类和FlyWithWings类实现它,Quack行为同理。接着修改Duck类,申明两个接口对象,添加PerformQuack方法和PerformFly方法来实现原先的Quack和Fly方法。
public abstract class Duck
{
public IFlyBehavior flyBehavior;
public IQuackBehavior quackBehavior;
public void PerformQuack()
{
quackBehavior.Quack();
}
public void PerformFly()
{
flyBehavior.Fly();
}
public abstract void Display();
}
让我们来调用看看:
class Program
{
static void Main(string[] args)
{
var mallard = new MallardDuck();
mallard.PerformQuack();
mallard.PerformFly();
}
}
结果是正确的,绿头鸭会呱呱叫也会飞。我们接着优化一下,可以动态的设定行为。
public abstract class Duck
{
public IFlyBehavior flyBehavior;
public IQuackBehavior quackBehavior;
public void PerformQuack()
{
quackBehavior.Quack();
}
public void PerformFly()
{
flyBehavior.Fly();
}
public abstract void Display();
public void setFlyBehavior(IFlyBehavior fb)
{
flyBehavior = fb;
}
public void setQuackBehavior(IQuackBehavior qb)
{
quackBehavior = qb;
}
}
新加setFlyBehavior和setQuackBehavior方法,让我们可以动态的传入鸭子的行为,接着我们来运行一下
class Program
{
static void Main(string[] args)
{
var mallard = new MallardDuck();
mallard.PerformQuack();
mallard.PerformFly();
mallard.setFlyBehavior(new FlyNoWay());
mallard.setQuackBehavior(new QuackNoWay());
mallard.PerformQuack();
mallard.PerformFly();
}
}
策略模式:定义了算法族,分别封装起来,让它们之间可以互相替换,此模式让算法的变化独立于使用算法的客户。
结语
感觉没有对策略模式总结的很到位,第一次写技术的博客也没什么思路,基本按书本的流程走下来,废话偏多,文章也不够简洁,接下去会多多思考,多多总结,提炼出文章中的精华。