先熟悉一下重要的概念:
面向接口编程
这里的接口指的不是狭义上的Interface,而是说抽象层次高的编程,也就是父类级。可以是普通类、抽象类、接口(interface)等。实现面向接口编程的基础就是多态。多态有好几种意义,但最常用,也是暗指的就是:父类的引用可以指向子类的对象实例。那么这种情况下此实例在不进行类型转换时表面上是一个父类型,所以只能访问父类中公开的属性和行为等成员;但实际上这个实例的类型是子类类型,进行类型转换后,除了继承自父类的成员外,还带有自己的成员。
{
public void Show()
{
Console.WriteLine("father");
}
}
public class Dog : Animal
{
public void ShowMessage()
{
Console.WriteLine("son");
}
}
dog.Show();
(dog as Dog).ShowMessage();
(dog as Dog).Show();
dog对象直接可见的成员是父类Animal中的Show方法。dog引用指向子类Dog的实例,进行转化后,除了可见继承自父类的Show方法外,还有它扩展的方法:ShowMessage方法。
这里要说的设计模式是策略模式。以headfirst中的例子来分析。
场景:一个模拟鸭子的游戏。鸭子有两种行为:叫和游泳。同时还有一个行为就是显示当前鸭子的信息。游戏中有两种鸭子:野鸭(mallard)和红头鸭(redhead)。
(一)通过继承实现
现在设计一个鸭子父类Duck,它包含三个方法:Quack(嘎嘎叫),Swim(游泳),Display(显示鸭子对象的信息)。这个父类的抽象层次较高,现在只知道它是鸭子,而不确定它是哪种鸭子。
两种具体鸭子:MallardDuck,野鸭子;RedheadDuck,红头鸭子。这两种鸭子都是会飞会游泳的,但它们的名称不一样,那么,在采用继承时,Display在子类中实现,而公共的行为:Quack和Swim在父类中实现。
因为这里有父类中有行为实现,那么可用抽象类来定义父类。
{
public void Quack()
{
Console.WriteLine("嘎嘎嘎 ~");
}
public void Swim()
{
Console.WriteLine("游泳 ~");
}
public abstract void Display();
}
//野鸭
public class MallardDuck : Duck
{
public override void Display()
{
Quack();
Swim();
Console.WriteLine("一只野鸭子。");
}
}
//红头鸭
public class RedheadDuck : Duck
{
public override void Display()
{
Quack();
Swim();
Console.WriteLine("一只红头鸭子。");
}
}
{
Duck duck = new MallardDuck();
duck.Display();
}
问题(一):现在要为鸭子添加一种行为:飞
那么,当前系统中有两种具体的鸭子:野鸭和红头鸭,当然这两种鸭子都是会飞的,所以在父类中添加飞行为的实现即可。
{
Console.WriteLine("飞 ~");
}
问题(二):现在要增加一种鸭子种类:橡皮鸭(rubberduck)但它的叫不是嘎嘎的叫,而是吱吱的叫。
橡皮鸭通过继承父类实现了鸭子的所有行为:叫(嘎嘎叫),游泳,显示信息,飞。但这种鸭子是不会飞的。那么在这个子类中就多余了一种飞的成员。它不会飞,但却有飞的行为。如果多余一个飞的行为,那么在橡皮鸭的对象中调用飞的行为,那它就会飞了。
{
public override void Display()
{
Quack();
Swim();
Console.WriteLine("一只橡皮鸭子。");
}
}
现在橡皮鸭的显示信息为:
嘎嘎嘎~
游泳~
一只橡皮鸭子。
现在更改橡皮鸭子的叫行为由原来的嘎嘎改为吱吱。这个可以通过虚方法的重写实现。
Duck父类:
{
Console.WriteLine("嘎嘎嘎 ~");
}
RubberDuck子类:
{
//base.Quack();
onsole.WriteLine("吱吱吱 ~");
}
现在显示信息为:
吱吱吱~
游泳~
一只橡皮鸭子。
还可以在不改变父类方法改为虚方法的情况下,通过new来覆盖继承的Quack方法:
{
Console.WriteLine("吱吱吱 ~");
}
那么这个fly行为当然也可以屏掉。
{ }
问题(三):继续添加更多的鸭子种类,例如:诱饵鸭(木头做的),它不会飞也不会叫。
那么如果还用继承来实现,那这种鸭子会多出飞和叫行为,当然这些行为可以屏掉。
问题(四):为鸭子添加跳舞行为
这里对于行为飞和嘎嘎叫来说,不是所有的子类都具有这两个行为,如果通过继承父类,那么所有的子类中都会有这些行为,那么这种方式不是适当的解决方式;把这两个行为从父类中分离开来,独立设置为行为接口,子类有哪个行为就实现哪个接口,通过这种接口继承,可以满足只有具有某个行为的子类真的只具有这个行为,但如果子类多了,那会造成大量的代码重复。
设计原则:分离出变化的部分。
上边已经提到了把飞和嘎嘎叫这两个行为分离开来,做为独立的接口存在,但在子类中不通过继承实现接口的方式来实现。
实现:
(1)分离出变化的行为
建立两个接口(interface),用以抽象两个行为。这里可以用抽象类,但对于行为抽象来说,接口(interface)更具有意义。
{
void Fly();
}
public interface IQuack
{
void Quack();
}
在上边的IQuack接口中,行为叫是一个高层的抽象,它就是一个叫的行为,而不需要确定它是嘎嘎叫,所以,把这个接口改一下,用来表示叫行为(改个名字):
{
void CryOut();
}
(2)实现两种行为,建立行为类
{
public void Fly()
{
Console.WriteLine("飞~");
}
}
public class FlyNoWay : IFly
{
public void Fly()
{}
}
public class CryQuack : ICry
{
public void CryOut()
{
Console.WriteLine("嘎嘎嘎 ~");
}
}
public class CrySqueak : ICry
{
public void CryOut()
{
Console.WriteLine("吱吱吱 ~");
}
}
以上实现了飞和叫两种行为,飞的行为类有两种:能飞的和不能飞的;叫的行为类有两种:嘎嘎叫的和吱吱叫的。
(3)通过接口变量执行类的行为
在父类Duck中,设置飞行为和叫行为的变量,通过指向子类的实现,访问父类的成员。
{
protected IFly _fly;
protected ICry _cry;
public void PerformCry()
{
_cry.CryOut();
}
public void PerformFly()
{
_fly.Fly();
}
public abstract void Display();
}
(4)实现父类Duck的具体子类
public class MallardDuck : Duck
{
public MallardDuck()
{
_fly = new FlyWithWings();
_cry = new CryQuack();
}
public override void Display()
{
PerformFly();
PerformCry();
Console.WriteLine("一只野鸭子。");
}
}
//橡皮鸭
public class RubberDuck : Duck
{
public RubberDuck()
{
_fly = new FlyNoWay();
_cry = new CrySqueak();
}
public override void Display()
{
PerformFly();
PerformCry();
Console.WriteLine("一只橡皮鸭子。");
}
}
现在,在鸭子子类的构造器中,对行为进行了实例化,确定了行为的具体实现。野鸭类中的叫这里设置了具体的嘎嘎叫行为,但说不定会因为某些原因(受伤)而叫不出声音或叫出嗷嗷叫的声音,那么在这里可以由具体实现提升到对接口编程。改造一下:
{
public MallardDuck()
{
}
public MallardDuck(IFly fly,ICry cry)
{
_fly = fly;
_cry = cry;
}
public override void Display()
{
PerformFly();
PerformCry();
Console.WriteLine("一只野鸭子。");
}
}
//橡皮鸭
public class RubberDuck : Duck
{
public RubberDuck()
{
}
public RubberDuck(IFly fly, ICry cry)
{
_fly = fly;
_cry = cry;
}
public override void Display()
{
PerformFly();
PerformCry();
Console.WriteLine("一只橡皮鸭子。");
}
}
那么这时,即使将来野鸭子的叫行为发生改变,那也可以通过设置另一种叫的行为类来改变叫的行为,而不需要改变子类的实现代码。
duck.Display();
飞~
嘎嘎嘎~
一只野鸭子。
看以上代码,发现对于两个参数的构造器来说,代码实现都是相同的,那这部分代码就重复了,可以移到父类中实现。
{
_fly = fly;
_cry = cry;
}
{}
策略模式
定义了算法家族,算法间可以互相替换,算法的变化独立于使用它的客户。
这里所说的算法指的就是上面的行为接口。
总结:策略模式是行为类设计模式,用于封装算法家庭,通过多态实现算法间互换和独立于使用客户。
