[转载]设计引导---一个鸭子游戏引发的设计(多态,继承,抽象,接口,策略者模式) - 肅 - 博客园

[转载]设计引导—一个鸭子游戏引发的设计(多态,继承,抽象,接口,策略者模式) – 肅 – 博客园.

  这篇博文是从实际生活中,提炼出来的设计理念,它现在是骨架,现在我加以代码实例,完成程序的血肉,以求让大家活生生的体会设计中的精髓。

自从我们学习面向对象编程以来,它方便了我们的思维思考模式,一个事物具备什么,就以对应的属性及方法加之。

( ̄▽ ̄) 没有什么难的,但是你学到的是最基础的语法和连自己都不是很了解的语言,用一段C语言程序,你可以很轻松的把它改成C#,JAVA等,这有什么难的?大多 数程序员们扭曲了C#语言,把C的语法都移植到C#上(在我不了解C#的时候,我自己都这么做过),错了不可怕,可怕的是错了还不肯改。

语言是一种工具,学会了都是想通的,但是设计思想不同决定了语言的本质区别。

进入正题,一步一步来剖析一个简单的鸭子游戏程序。

首先设计一个鸭子对象,是不是?大致这样:

复制代码
 1     public class Duck  2  {  3         void quack(){  4             //...鸭子都会叫
 5  }  6         void swim(){  7            //...都会游泳
 8  }  9         void Display() { 10         //...外观
11  } 12     }
复制代码

然后鸭子游戏中有各种鸭子一边游泳戏水,一边呷呷叫,各种鸭子都继承Duck类哦,游戏在预料之中运行。

这应该是标准的OO(Object Oriented)技术吧?游戏完美运行中………

目前鸭子会叫会游泳,都在水里多没意思?来个创新吧:

丑小鸭也能飞上青天??o(∩_∩)o

现在想要鸭子飞,那么就要给鸭子添加一个飞行方法,好比这样:

复制代码
    public class Duck { void quack(){ //...鸭子都会叫
 } void swim(){ //...都会游泳
 } void Display() { //...外观
 } void Fly() { //...飞行
 } }
复制代码

方法已加,游戏中的小鸭子们可以飞咯。

现在问题,才刚刚出现:

在演示程序的时候,“橡皮假鸭”在屏幕上飞来飞去,游戏里面有各种各样的鸭子。

当没有Fly()的时候,小鸭子们可以很平稳的运行。在父类中加上Fyl(),会导致所有的子类都具备Fly(),连那些不该具备的子类也无法免除,所以:

对代码所做的局部修改,影响层面可不只是局部。

看看这张图,说不定和你的想法不谋而合:

覆盖掉“橡皮鸭”的飞行方式。这是个不错的选择,这样一来,“橡皮鸭”也不会到处乱飞了~~(注意哦“橡皮鸭”会叫的–“吱吱”)。

游戏中现在又加入一种鸭子~问题又来啦~~

现在加入成员是-“诱饵鸭”(DecoyDuck)它是木头做的假鸭,它不会飞当然也不会叫~

OK,现在对于这个新成员,就这么做:

继续覆盖它的方法,它只有老老实实的在水里面游!

你们觉得这种繁琐的工作,什么时候才是个头呢?鸭子种类无限,你的噩梦无限~继承这个解决方法,看来果断不行啊,要换要换。

你觉得这个设计怎么样:

我定义一些接口,目前先做两个,一个Flyable,一个Quackable:

Duck类也改掉,只包含两个方法:Swim(),Display():

然后让不同的子类再继承Duck类的时候,分别实现一下Fly()和Quack(),接口也用上了,你觉得怎么样?

好像有点用,但是,再换个大的角度想,子类继承实现的那些Fly(),Quack()都是些重复代码,然而,重复代码是可以接受的,但是,在你维护的时候,假如有30个Duck子类吧,要稍稍修改一下那个Fly(),有没有觉得可维护性瞬间就低到下限?

在这个新的设计方法中,虽然解决了“一部分”问题,但是,这造成了代码无法复用!有没有觉得?还有更可怕的哦,会飞的鸭子,那飞行动作可不是千篇一律的,来个空翻360°旋转这个动作,你又要怎么做?o(∩_∩)o

不管你在何处工作,用何种编程语言,在软件开发上,一直伴随你的那个不变真理是什么? (把你想到的答案,写在评论上吧^_^,期待你的回答

把这个先前的设计都清零……

现在我们知道使用继承并不能很好的解决问题,因为鸭子的行为在子类里不断地改变,并且让那些子类都有这些行为是不恰当的,Flyable和 Quackable接口似乎不错,解决了问题(只有会飞的鸭子才继承Flyable),但是这依旧让你有很多任务去做,你依旧不能做到代码复用,你在维护 的时候,依旧要往下追踪,一 一去修改对应的行为。

对于这个问题,现在真正有个设计原则,能解决这个问题,它能实现代码复用,能添加和修改使系统变得更有弹性。

设计原则:

    找出应用中可能需要变化之处,把它们独立出来,不要和那些不需要变化的代码混在一起。

这是些理论知识,对于骨架,我会丰满出它的羽翼。继续看吧,你会有收获!

现在,是时候取出Duck类中的变化的部分了!

目前可变的是fly和quack相关部分,它们会变化,现在单独把这两个行为从Duck类中分开,建立一种组新类代表每个行为。

先做个飞行行为的接口:

1    public interface FlyBehavior 2  { 3         void Fly(); 4     }

呷呷叫行为的接口:

1    public interface QuackBehavior 2  { 3        void quack(); 4     }

是否听说过这么一个设计理念:

  针对接口编程,而不是针对实现编程。

而“针对接口编程”真正的意思是“针对抽象类编程”。

“针对接口编程”的关键就在多态。利用多态,程序可以在针对抽象类编程,执行时会根据实际状况执行到真正的行为,不会被绑死在抽象类的行为上。

再深挖一点,“针对抽象类编程”这句话,可以更明确地说成“变量的声明类型,应该是抽象类型,这可以是一个抽象类,或是一个接口”!不理解没关系!接下来我们用程序来让大家慢慢吃透这个概念!

举个传统的例子:

针对实现编程:

1 Dog d = new Dog(); 2 d.bark();//“汪汪”叫行为

针对接口或抽象类编程:

Animal animal = new Dog(); animal.makeSound();//这个方法实现“汪汪”叫

这个不明白?没关系,有图:

现在让我们来重新实现鸭子游戏中的设计吧!

先设计飞行行为:

复制代码
    class FlyWithWings:FlyBehavior { public void Fly() { Console.WriteLine("我会飞啦~!"); } } class FlyNoWay : FlyBehavior { public void Fly() { //什么都不做,它不会飞
 } }
复制代码

我把两个类放在一起了,这方便大家阅读,实际上应该分开的。

再看看“呷呷”叫行为:

 

复制代码
 1     class Quack : QuackBehavior  2  {  3        public void quack()  4  {  5             Console.WriteLine("呷呷!");  6  }  7  }  8 
 9     class Squeak : QuackBehavior 10  { 11        public void quack() { 12             Console.WriteLine("吱吱!");//橡皮鸭
13  } 14  } 15 
16     class MuteQuack:QuackBehavior 17  { 18        public void quack() 19  { 20             Console.WriteLine(".......");//"诱饵鸭"不会叫
21  } 22     }
复制代码

行为做好了~来实现Duck类

 

复制代码
 1     public abstract class Duck  2  {  3         public FlyBehavior flybehavior;  4         public QuackBehavior quackbehavior;  5 
 6         public void performQuack() {  7  quackbehavior.quack();  8  }  9         public void performFly() 10  { 11  flybehavior.Fly(); 12  } 13         public virtual void Swim(){ 14             Console.WriteLine("~~游~~"); 15  } 16         public virtual void Display(){} 17     }
复制代码

结构很简单,不是吗?定义QuackBehavior,FlyBehavior,每只鸭子都会引用实现QuackBehavior接口对象,让它们来处理鸭子的行为。

想要呷呷叫的效果,就要quackbehavior对象去呷呷叫就可以了,我们现在不用再关心quackbehavior接口的对象是什么,只要关系Duck如何叫就行了。

这个quackbehavior接口可以重用了哦。有没有发现?在什么地方可以重用呢?思考下,我后面再提。
好了,现在来具体实现鸭子实体了:

复制代码
 1     public class MallarDuck : Duck  2  {  3         public MallarDuck() {  4             quackbehavior = new Quack();  5             flybehavior = new FlyWithWings();  6  }  7         public override void Display()  8  {  9             Console.WriteLine("我是一只美丽的绿头鸭!"); 10  } 11     }
复制代码

o(∩_∩)o大功就要告成了,  看Program:

复制代码
1         static void Main(string[] args) 2  { 3             MallarDuck mallard = new MallarDuck(); 4  mallard.Display(); 5  mallard.Swim(); 6  mallard.performQuack(); 7  mallard.performFly(); 8 
9         }
复制代码

一目了然,这个程序要做什么,怎么做,很简单吧?

看看运行结果:

代码也贴完了,程序确实可以运行,现在看下这个设计的最后一个概念:

  多用组合,少用继承。

正如你看见的,使用组合建立系统具有很大的弹性,不仅仅将算法族封装成类,更可以在“运行时动态地改变行为”。

不知道什么是“运行时动态地改变行为”??

好,那我再演示一个,就拿那美丽的绿头鸭做例子:

Duck类最新修改:

复制代码

 1  public abstract class Duck  2  {  3         public FlyBehavior flybehavior;  4         public QuackBehavior quackbehavior;  5 
 6         public void performQuack() {  7  quackbehavior.quack();  8  }  9         public void performFly() 10  { 11  flybehavior.Fly(); 12  } 13         public virtual void Swim(){ 14             Console.WriteLine("~~游~~"); 15  } 16         public virtual void Display(){} 17 
18         public void SetFlyBehavior(FlyBehavior flyb)//额外添加
19  { 20             flybehavior = flyb; 21  } 22 
23     }
复制代码

然后我再添加一个火箭动力:

复制代码
    class FlyRockePowered : FlyBehavior { public void Fly() { Console.WriteLine("打了鸡血!4200米/秒,加速飞行!"); } }
复制代码

看看Program:

复制代码
 1     class Program  2  {  3         static void Main(string[] args)  4  {  5             MallarDuck mallard = new MallarDuck();  6  mallard.Display();  7  mallard.Swim();  8  mallard.performQuack();  9  mallard.performFly(); 10 
11             mallard.SetFlyBehavior(new FlyRockePowered()); 12  mallard.performFly(); 13  } 14     }
复制代码

结果:

动态添加了吧?修改一下很容易吧?

至于那个quackbehavior接口重用问题

鸭鸣器知道吧?猎人用这个东西模拟鸭子叫,引诱野鸭,这个不是个很好的重用吗?o(∩_∩)o  更多重用只局限于你的想象~

如果你认真看完了这个,那么下面这个奖章是给予你的:

你学会了策略者设计模式o(∩_∩)o

你再也不用担心系统遇到任何变化。

 策略者模式

  定义了算法族,分别封装起来,让它们之间可以相互替换,此模式让算法的变化独立于使用算法的用户。

 

赞(0) 打赏
分享到: 更多 (0)

觉得文章有用就打赏一下文章作者

支付宝扫一扫打赏

微信扫一扫打赏