该过年了,总让人有点浮躁,公司就省俩人了,唉,如果坐等时间,那实在难熬,只能给自己找点事做,转移一下注意力。进入今天的主题吧策略模式。
策略模式定义了算法族,分别封装起来,让它们之间可以相互替换,此模式让算法的变化独立于使用算法的客户。
模拟鸭子的简单应用
Joe上班的公司做了一套相当成功的模拟鸭子游戏SimUDuck,游戏中出现各种鸭子,一边游戏戏水,一边呱呱叫。此系统的内部设计使用了标准的OO技术,设计了一个鸭子超类,并让各种鸭子继承此超类。
让鸭子能飞
去年,公司的竞争力加剧,公司主管认为该是创新的时候了。主管认为,此模拟程序需要会飞的鸭子,将竞争者抛在后面。
改进继承
Joe认识到继承可能不是一个好的解决办法,因为他刚刚拿到来自主管的备忘录,希望以后每六个月更新产品(至于更新办法,他们还没想到)。Joe知道规格会常常改变,每当有新的鸭子子类出现,他就要被迫检视并可能需要覆盖fly()和quack().....这简直是无穷尽的噩梦。所以,他需要一个更清晰的方法,让某些(而不是全部)鸭子类型可飞或可叫。
其实,并非所有的鸭子子类都具有飞行和呱呱叫的行为,所以继承并不是适当的解决方式。虽然Flyable与Quackable可以解决一部分的问题(不会再有会飞的橡皮鸭),但是却造成代码无法复用,这只能算是从一个噩梦跳进另一个噩梦。甚至,在会飞的鸭子中,飞行的动作可能还有多种变化......
现在我们知道使用继承有一些缺失,因为改变鸭子的行为会影响所有种类的鸭子,而这并不恰当。Flyable与Quackable接口一开始似乎还挺不错,解决了问题(只有会飞的鸭子才继承Flyable),但是接口不具有实现代码,所以继承接口无法达到代码的复用。这意味着:无论何时你需要修改某个行为,你被迫得往下追踪并修改每一个有定义此行为的类,一不小心,可能造成新的错误。
设计原则:找出应用中可能需要变化之处,把它们独立出来,不要和那些不需要变化的代码混在一起。这个概念很简单,几乎是每个设计模式背后的精神所在,所有的模式都提供了一套方法让系统中的某部分改变不会影响其它部分。
分开变化和不变化的部分
为了要分开变化和不变化的部分,我们准备建立两组类,一个是fly相关,一个是quack相关的,每一组类将实现各自的动作。比如说,我们可能有一个类实现“呱呱叫”,另一个类实现“叽叽叫”,另一个类实现“安静”。
设计鸭子行为
我们利用接口代表每个行为,比方说,IFlyBehavior与IQuackBehaivor,而行为的每个实现都必须实现这些接口之一。所以这次鸭子类不会负责实现fly与quack接口,而是由其他类专门实现IFlyBehavior与IQuackBehaivor,这就称为“行为类”。由行为类实现行为接口,而不是由Duck类实现接口。
这样的做法迥异于以往,以前的做法是:行为是继承自Duck超类的具体实现而来,或是继承某个接口并由子类自行实现而来。这两种做法都是依赖于实现,我们被实现绑的死死,没办法更改行为(除非写更多的代码)。
针对接口编程,而不是针对实现编程。
关于接口编程和实现编程:
假设有一个抽象类Animal,有两个具体的实现(Dog与Cat)继承自Animal。
针对实现编程的作法如下:
1 Dog d = new Dog(); 2 d.bark();
针对接口/超类型编程作法如下:
1 Animal animal = new Dog(); 2 animal.makeSound();
子类型实例化的动作是“在运行时才指定具体实现的对象”
1 a = getAnimal(); 2 a.makeSound();
这样的设计,可以让飞行和呱呱叫的动作被其他的对象复用,因为这些行为已经与鸭子无关了。而我们可以新增一些行为,不会影响到既有的行为类,也不会影响有使用到飞行行为的鸭子类。
集成鸭子的行为
鸭子现在会将飞行和呱呱叫的动作,委托(delegate)别人处理,而不是使用定义在自己类(或子类)内的方法。
① 首先,在鸭子中加入两个实例变量, 分别为FlyBehavior与QuackBehavior,声明为接口类型(而不是具体类实现类型),每个变量会利用多态的方式在运行时引用正确的行为类型(例如:FlyWithWings、Squeak . . . 等)。
我们也必须将Duck类与其所有子类中的fly() 与quack( ) 移除,因为这些行为已经被搬移到FlyBehavior与QuackBehavior类中了。我们用performFly()和performQuack()取代Duck类中的fly()与quack()。
1 using System; 2 using System.Collections.Generic; 3 using System.Linq; 4 using System.Text; 5 using System.Threading.Tasks; 6 7 namespace Wolfy.设计模式 8 { 9 public abstract class Duck 10 { 11 //为行为接口类型声明两个引用变量,所有鸭子子类都继承它们。 12 public IFlyBehavior flyBehavior; 13 //每只鸭子都引用实现QuackBehavior接口的对象。 14 public IQuackBehavior quackBehavior; 15 public Duck() { } 16 public abstract void Display(); 17 public void PerformFly() 18 { 19 //委托给行为类 20 flyBehavior.Fly(); 21 } 22 public void PerformQuack() 23 { 24 //鸭子将呱呱叫行为委托给quackBehavior引用的对象。 25 quackBehavior.Quack(); 26 } 27 public void Swim() 28 { 29 Console.WriteLine("会游泳....."); 30 } 31 } 32 }
1 using System; 2 using System.Collections.Generic; 3 using System.Linq; 4 using System.Text; 5 using System.Threading.Tasks; 6 7 namespace Wolfy.设计模式 8 { 9 public interface IFlyBehavior 10 { 11 //所有飞行行为必须实现的接口。 12 void Fly(); 13 } 14 }
1 using System; 2 using System.Collections.Generic; 3 using System.Linq; 4 using System.Text; 5 using System.Threading.Tasks; 6 7 namespace Wolfy.设计模式 8 { 9 public interface IQuackBehavior 10 { 11 void Quack(); 12 } 13 }
1 using System; 2 using System.Collections.Generic; 3 using System.Linq; 4 using System.Text; 5 using System.Threading.Tasks; 6 7 namespace Wolfy.设计模式 8 { 9 public class FlyWithWings : IFlyBehavior 10 { 11 #region IFlyBehavior 成员 12 13 public void Fly() 14 { 15 Console.WriteLine("会飞......"); 16 } 17 18 #endregion 19 } 20 }
1 using System; 2 using System.Collections.Generic; 3 using System.Linq; 4 using System.Text; 5 using System.Threading.Tasks; 6 7 namespace Wolfy.设计模式 8 { 9 public class FlyNoWay : IFlyBehavior 10 { 11 #region IFlyBehavior 成员 12 13 public void Fly() 14 { 15 Console.WriteLine("不会飞......"); 16 } 17 18 #endregion 19 } 20 }
1 using System; 2 using System.Collections.Generic; 3 using System.Linq; 4 using System.Text; 5 using System.Threading.Tasks; 6 7 namespace Wolfy.设计模式 8 { 9 public class MuteQuack : IQuackBehavior 10 { 11 #region IQuackBehavior 成员 12 13 public void Quack() 14 { 15 Console.WriteLine("不会叫"); 16 } 17 18 #endregion 19 } 20 }
1 using System; 2 using System.Collections.Generic; 3 using System.Linq; 4 using System.Text; 5 using System.Threading.Tasks; 6 7 namespace Wolfy.设计模式 8 { 9 public class Quack : IQuackBehavior 10 { 11 #region IQuackBehavior 成员 12 13 void IQuackBehavior.Quack() 14 { 15 Console.WriteLine("呱呱叫......"); 16 } 17 18 #endregion 19 } 20 }
1 using System; 2 using System.Collections.Generic; 3 using System.Linq; 4 using System.Text; 5 using System.Threading.Tasks; 6 7 namespace Wolfy.设计模式 8 { 9 public class Squeak : IQuackBehavior 10 { 11 #region IQuackBehavior 成员 12 13 public void Quack() 14 { 15 Console.WriteLine("吱吱叫......."); 16 } 17 18 #endregion 19 } 20 }
1 using System; 2 using System.Collections.Generic; 3 using System.Linq; 4 using System.Text; 5 using System.Threading.Tasks; 6 7 namespace Wolfy.设计模式 8 { 9 /// <summary> 10 /// MallardDuck从Duck继承,具有flyBehavior 和quackBehavior 实例变量。 11 /// </summary> 12 public class MallardDuck : Duck 13 { 14 public MallardDuck() 15 { 16 //FlyWithWings作为IFlyBehavior类型 17 base.flyBehavior = new FlyWithWings(); 18 //Quack类处理呱呱叫。 19 base.quackBehavior = new Quack(); 20 } 21 public override void Display() 22 { 23 Console.WriteLine("绿头鸭......"); 24 } 25 } 26 }
1 using System; 2 using System.Collections.Generic; 3 using System.Linq; 4 using System.Text; 5 using System.Threading.Tasks; 6 7 namespace Wolfy.设计模式 8 { 9 public class WoodDuck : Duck 10 { 11 public WoodDuck() 12 { 13 flyBehavior = new FlyNoWay(); 14 quackBehavior = new MuteQuack(); 15 } 16 public override void Display() 17 { 18 Console.WriteLine("木头鸭子....."); 19 } 20 } 21 }
1 using System; 2 using System.Collections.Generic; 3 using System.Linq; 4 using System.Text; 5 using System.Threading.Tasks; 6 7 namespace Wolfy.设计模式 8 { 9 class Program 10 { 11 static void Main(string[] args) 12 { 13 Duck mallard = new MallardDuck(); 14 mallard.Display(); 15 //调用MallardDuck继承来的perform方法,进而将绿头鸭的行为委托给quack和fly的行为类来处理。 16 mallard.PerformFly(); 17 mallard.PerformQuack(); 18 mallard.Swim(); 19 Duck wood = new WoodDuck(); 20 wood.Display(); 21 wood.PerformFly(); 22 wood.PerformQuack(); 23 wood.Swim(); 24 Console.Read(); 25 26 } 27 } 28 }
所有鸭子从Duck继承,飞行行为实现FlyBehavior接口,呱呱叫行为实现QuackBehavior接口。
“有一个”(has a)可能比“是一个”(is a)更好
有一个关系相当有趣:每一鸭子都有一个FlyBehavior且有一个QuackBehavior,让鸭子将飞行和呱呱叫委托它们代为处理。
如果将两个类结合起来使用(如同本例),这就是组合(Composition)。这种作法和继承不同的地方在于:鸭子的行为不是继承而来,而是和适当的行为对象组合而来。
第三个设计原则
多用组合,少用继承
使用组合建立系统具有很大的弹性,不仅可将算法族封装成类,更可以在运行时动态地改变行为。
优缺点