根据V 0.0.0 .2需求封装的类,此时需求再次变化———V0.0.0.3,要求有一个橡皮鸭子
此时,设计类图为:
其中,叫声不再是”呱呱”而是”吱吱”。
代码为:
public class RubberDuck:Duck
{
public override void display()
{
Console.WriteLine("我是一个橡皮鸭");
}
public new void quack()
{
Console.WriteLine("我吱吱叫");
}
}
测试类:
#region 测试绿头鸭
MallarDuck md = new MallarDuck();
md.display();
md.quack();
md.swim();
md.fly();
#endregion
Console.WriteLine(" ");
#region 测试红头鸭
RedHeadDuck rd = new RedHeadDuck();
rd.display();
rd.quack();
rd.swim();
rd.fly();
#endregion
Console.WriteLine(" ");
#region 测试橡皮鸭
RubberDuck rd1 = new RubberDuck();
rd1.display();
rd1.quack();
rd1.swim();
rd1.fly();
#endregion
Console.ReadKey();
显示:
发现塑料鸭也飞起来了,这不符合需求。
现在的解决方法是:
把超类Duck的fly()方法重载掉。即:
public class RubberDuck:Duck
{
public override void display()
{
Console.WriteLine("我是一个橡皮鸭");
}
public new void quack()
{
Console.WriteLine("我吱吱叫");
}
public new void fly()
{
}
}
此时,逻辑上是正确的。
但是,如果由于需求始终在变,如果每次增加一个Duck的子类,都需要去考虑覆盖对应的方法,一旦忘记覆盖或者覆盖错误,那么将是一场噩梦。那么彻底放弃该设计,我们再次重新开始。
此时,想到了一个稍好一些的设计
将经常变化的fly()方法和quack()方法抽象成为接口,这样不同的子类继承接口,通过不同的程序实现能解决一些问题,例如橡皮鸭不能fly(不让RubberDuck类实现Flyable接口即可)。
对于设计图如下:
以上是MallarDuck绿头鸭类的实现,可见MallarDuck继承Duck类,实现Flyable、Quackable接口。
代码为:
class MallarDuck:Duck,Flyable,Quackable
{
public void quack()
{
Console.WriteLine("我呱呱叫");
}
public override void display()
{
Console.WriteLine("我显示为绿头鸭");
}
public void fly()
{
Console.WriteLine("我可以飞起来");
}
}
显示为:
此时,添加红头鸭类,设计如下:
代码为:
public class RedHeadDuck:Duck,Flyable,Quackable
{
public override void display()
{
Console.WriteLine("我显示为红头鸭");
}
public void quack()
{
Console.WriteLine("我呱呱叫");
}
public void fly()
{
Console.WriteLine("我可以飞起来");
}
}
测试显示:
再添加塑料鸭,不同之处在于塑料鸭不能飞,因此不要实现Flyable接口
代码:
public class RubberDuck:Duck,Quackable
{
public override void display()
{
Console.WriteLine("我是一个橡皮鸭");
}
public new void quack()
{
Console.WriteLine("我吱吱叫");
}
}
测试显示:
逻辑上,完全合乎需求,并且解决了RubberDuck类继承超类而不得不覆盖掉fly()方法的问题(通过不让RubberDuck类实现Flyable接口)
这时,我们会发现使用接口带来的弊端:
对于实现Flyable接口以及Quackable接口的类,在实现时不得不对相同的功能,多次重复编写代码,无法复用,例如:
RedHeadDuck类和MallarDuck类都有如下代码:
public void quack()
{
Console.WriteLine("我呱呱叫");
}
public void fly()
{
Console.WriteLine("我可以飞起来");
}
使用接口并实现这些接口,虽然不会出现没有覆盖基类方法而出现的逻辑错误,这种使用“依赖”来实现,必然导致无法复用,如果当基类很多时,这个设计就又会出现代码无法复用的缺点。
下个要解决的问题就是如何复用了,来根烟或者来杯咖啡,心脏不好的就来个救心丸,再次彻底放弃上边的设计。
这次,我们不再用Duck的子类来实现Flyable接口、Quackable接口,而是由一组行为类来实现,这组类将所有fly和quack的方法统一实现 ,而不是由鸭子子类来实现,而是在鸭子子类中调用这些实现,这样复用了代码。
行为类的设计:
超类Duck设计为:
代码为:
public abstract class Duck
{
public FlyBehavior flyBehavior;
public QuackBehavior quackBehavior;
public void swim()
{
Console.WriteLine("我可以游泳");
}
public abstract void display();
public void performQuack()
{//所有鸭子都叫,至于是呱呱、吱吱还是无声,在基类中确定
quackBehavior.quack();
}
public void performFly()
{//所有鸭子都飞,至于是真飞还是不飞,在基类中确定
flyBehavior.fly();
}
}
现在创建绿头鸭类:
代码为:
class MallarDuck:Duck
{
public MallarDuck()
{
//指定绿头鸭是真飞
flyBehavior = new FlyWithWings();
//指定绿头鸭是呱呱叫
quackBehavior = new Quack();
}
public override void display()
{
Console.WriteLine("我显示为绿头鸭");
}
}
测试显示为:
同样,添加红头鸭
代码为:
class RedHeadDuck:Duck
{
public RedHeadDuck()
{
//红头鸭指定为真飞
flyBehavior = new FlyWithWings();
//红头鸭指定为吱吱叫
quackBehavior = new Squeak();
}
public override void display()
{
Console.WriteLine("我显示为红头鸭");
}
}
不再添加RubberDuck,自此我们可以看出,子类中不再出现重复的业务功能代码,这种设计大大提高了代码的复用性。
以绿头鸭为例,看一下程序的执行过程:
(1) Duck md = new MallarDuck(),调用MallarDuck类的构造函数,
此时,将该类中的两个字段赋值:
public MallarDuck()
{
flyBehavior = new FlyWithWings();
quackBehavior = new Quack();
}
此时, flyBehavior、quackBehavior持有FlyWithWings()、Quack()类实例的引用。
(2) 调用md.display()方法,执行MallarDuck类中重载的display()方法
public override void display()
{
Console.WriteLine("我显示为绿头鸭");
}
(3) 调用md.performQuack(),由于MallarDuck类没有重载该方法,因此调用其父类方法
public void performQuack()
{
quackBehavior.quack();
}
由于quackBehavior持有Quack()类实例的引用,因此调用Quack类中的实现了
class Quack:QuackBehavior
{
public void quack()
{
Console.WriteLine("我呱呱叫");
}
}
这个设计看上去确实不错,你不会再需要为所有子类,编写大量重复的代码了。