设计引导--一个鸭子游戏引发的设计理念(多态,继承,抽象,接口,策略者模式)

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

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

(▽) 没有什么难的,但是你学到的是最基础的语法和连自己都不是很了解的语言,用一段C语言程序,你可以很轻松的把它改成C#,JAVA等,这有什么难的?大多数程序员们扭曲了C#语言,把C的语法都移植到C#上(在我不了解C#的时候,我自己都这么做过),错了不可怕,可怕的是错了还不肯改。
语言是一种工具,学会了都是想通的,但是设计思想不同决定了语言的本质区别。
进入正题,一步一步来剖析一个简单的鸭子游戏程序。
 
首先设计一个鸭子对象,是不是?大致这样:


代码如下:

public class Duck
{
void quack(){
//...鸭子都会叫
}
void swim(){
//...都会游泳
}
void Display() {
//...外观
}
}

然后鸭子游戏中有各种鸭子一边游泳戏水,一边呷呷叫,各种鸭子都继承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类中分开,建立一种组新类代表每个行为。
先做个飞行行为的接口:
public interface FlyBehavior
{
void Fly();
}
呷呷叫行为的接口:
public interface QuackBehavior
{
void quack();
}
是否听说过这么一个设计理念
针对接口编程,而不是针对实现编程。
而“针对接口编程”真正的意思是“针对抽象类编程”。
“针对接口编程”的关键就在多态。利用多态,程序可以在针对抽象类编程,执行时会根据实际状况执行到真正的行为,不会被绑死在抽象类的行为上。
再深挖一点,“针对抽象类编程”这句话,可以更明确地说成“变量的声明类型,应该是抽象类型,这可以是一个抽象类,或是一个接口”!不理解没关系!接下来我们用程序来让大家慢慢吃透这个概念!
举个传统的例子
针对实现编程:
Dog d = new Dog();
d.bark();//“汪汪”叫行为
针对接口或抽象类编程:
Animal animal = new Dog();
animal.makeSound();//这个方法实现“汪汪”叫
这个不明白?没关系,有图:

现在让我们来重新实现鸭子游戏中的设计吧!
先设计飞行行为:


代码如下:

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

我把两个类放在一起了,这方便大家阅读,实际上应该分开的。
再看看“呷呷”叫行为:


代码如下:

class Quack : QuackBehavior
{
public void quack()
{
Console.WriteLine("呷呷!");
}
}
class Squeak : QuackBehavior
{
public void quack() {
Console.WriteLine("吱吱!");//橡皮鸭
}
}
class MuteQuack:QuackBehavior
{
public void quack()
{
Console.WriteLine(".......");//"诱饵鸭"不会叫
}
}

行为做好了~来实现Duck类


代码如下:

public abstract class Duck
{
public FlyBehavior flybehavior;
public QuackBehavior quackbehavior;
public void performQuack() {
quackbehavior.quack();
}
public void performFly()
{
flybehavior.Fly();
}
public virtual void Swim(){
Console.WriteLine("~~游~~");
}
public virtual void Display(){}
}

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

想要呷呷叫的效果,就要quackbehavior对象去呷呷叫就可以了,我们现在不用再关心quackbehavior接口的对象是什么,只要关系Duck如何叫就行了。
这个quackbehavior接口可以重用了哦。有没有发现?在什么地方可以重用呢?思考下,我后面再提。
好了,现在来具体实现鸭子实体了:


代码如下:

public class MallarDuck : Duck
{
public MallarDuck() {
quackbehavior = new Quack();
flybehavior = new FlyWithWings();
}
public override void Display()
{
Console.WriteLine("我是一只美丽的绿头鸭!");
}
}

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


代码如下:

static void Main(string[] args)
{
MallarDuck mallard = new MallarDuck();
mallard.Display();
mallard.Swim();
mallard.performQuack();
mallard.performFly();
}

一目了然,这个程序要做什么,怎么做,很简单吧?
看看运行结果:
 
代码也贴完了,程序确实可以运行,现在看下这个设计的最后一个概念:
多用组合,少用继承。

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

不知道什么是“运行时动态地改变行为”?
好,那我再演示一个,就拿那美丽的绿头鸭做例子:
Duck类最新修改:


代码如下:

public abstract class Duck
{
public FlyBehavior flybehavior;
public QuackBehavior quackbehavior;
public void performQuack() {
quackbehavior.quack();
}
public void performFly()
{
flybehavior.Fly();
}
public virtual void Swim(){
Console.WriteLine("~~游~~");
}
public virtual void Display(){}
public void SetFlyBehavior(FlyBehavior flyb)//额外添加
{
flybehavior = flyb;
}
}

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


代码如下:

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

看看Program:


代码如下:

class Program
{
static void Main(string[] args)
{
MallarDuck mallard = new MallarDuck();
mallard.Display();
mallard.Swim();
mallard.performQuack();
mallard.performFly();
mallard.SetFlyBehavior(new FlyRockePowered());
mallard.performFly();
}
}

结果:
 
动态添加了吧?修改一下很容易吧?
至于那个quackbehavior接口重用问题
鸭鸣器知道吧?猎人用这个东西模拟鸭子叫,引诱野鸭,这个不是个很好的重用吗?o(∩_∩)o 更多重用只局限于你的想象~
如果你认真看完了这个,那么下面这个奖章是给予你的:
你学会了策略者设计模式o(∩_∩)o
你再也不用担心系统遇到任何变化
策略者模式
定义了算法族,分别封装起来,让它们之间可以相互替换,此模式让算法的变化独立于使用算法的用户。
看完啦,如果觉得还不错,就点下推荐吧。o(∩_∩)o 这是对我的支持,谢谢

(0)

相关推荐

  • 设计引导--一个鸭子游戏引发的设计理念(多态,继承,抽象,接口,策略者模式)

    这篇博文是从实际生活中,提炼出来的设计理念,它现在是骨架,现在我加以代码实例,完成程序的血肉,以求让大家活生生的体会设计中的精髓. 自从我们学习面向对象编程以来,它方便了我们的思维思考模式,一个事物具备什么,就以对应的属性及方法加之. (▽) 没有什么难的,但是你学到的是最基础的语法和连自己都不是很了解的语言,用一段C语言程序,你可以很轻松的把它改成C#,JAVA等,这有什么难的?大多数程序员们扭曲了C#语言,把C的语法都移植到C#上(在我不了解C#的时候,我自己都这么做过),错了不可怕,可怕的

  • 用js做一个小游戏平台 (一)

    记得上班写代码时,我们技术总监总说是要先"设计",那就先"设计"吧. ps:我是新手大家多多见谅. .网页游戏区域.就是说需要知道游戏在网页上的区域,如下: 在网页中插入一个div,设定宽高和id, <div id="GameFrame" style="width:400;height:400"></div> 然后再js中得到该对象, var _GameFrame = document.getEleme

  • Java编程经典小游戏设计-打砖块小游戏源码

    [程序中使用的数据结构和符号说明] HitBrick类 GreenBallThread控制小球路线 xUp,yUp,bouncing定义变量存储16位数值形式 x,y小球坐标 xDx,yDy坐标增量 MAX_X,MAX_Y坐标最大值 renew初始化 label标签 Rx,Ry横板坐标 Brick[]砖块 ball小球 HitBrick()定义小球横板及砖块位置坐标 keyPressd(keyEent)定义小球启动键(按空格键启动) keyReleased(keyEvent)接收键盘事件侦听器接

  • 使用Python写一个小游戏

    引言 最近python语言大火,除了在科学计算领域python有用武之地之外,在游戏.后台等方面,python也大放异彩,本篇博文将按照正规的项目开发流程,手把手教大家写个python小游戏,来感受下其中的有趣之处.本次开发的游戏叫做alien invasion. 安装pygame并创建能左右移动的飞船 安装pygame 本人电脑是windows 10.python3.6,pygame下载地址: 传送门 请自行下载对应python版本的pygame 运行以下命令 $ pip install wh

  • python实现一个猜拳游戏

    本文实例为大家分享了python实现一个猜拳游戏的具体代码,供大家参考,具体内容如下 设计一个猜拳的游戏,要求如下: 1.游戏开始时,输入玩家名字 2.可以选择3个电脑对手(电脑名字自定) 3.游戏规则为:1.剪刀 2.石头 3.布 4.游戏开始,玩家vs电脑,玩家选择出一个,电脑随机1-3 5.具有局数统计(如:现在是第几局).输赢统计(玩家胜几局,电脑胜几局) 效果图如下: 代码如下: import random #先导入随机数 class game(): #创建一个游戏的类 里面分为两个属

  • Redis中一个String类型引发的惨案

    ​ 曾经看到这么一个案例,有一个团队需要开发一个图片存储系统,要求这个系统能快速记录图片ID和图片存储对象ID,同时还需要能够根据图片的ID快速找到图片存储对象ID.我们假设用10位数来表示图片ID和图片存储对象ID,例如图片的ID为1101021043,它所对应的图片存储对象的ID为2301010051,可以看到图片ID和图片存储ID正好是一一对应的,是典型的key-value形式,所以首先会想到直接使用String类型来保存数据.把图片ID和图片存储ID分别作为键值对的key和value来保

  • 一步步教你使用JavaScript打造一个扫雷游戏

    目录 前言 一.思路分析 二.静态页面搭建 2.1 结构层 2.2 样式层 三.js页面交互 3.1 获取元素及变量初始化 3.2 10个雷的初始化设置 3.3 游戏开始事件封装 3.4 核心事件函数封装 3.5 游戏开始 四.总结 前言 扫雷大家都玩过,今天我们就是用JavaScript来打造扫雷游戏.废话不多说,直接看下效果; 上图是失败后的结果. 一.思路分析 我们新建一个首页,在首页放置一个点击开始游戏的按钮,动态生成100个小格,即100div;然后通过点击div进行扫雷操作,然后扫雷

  • Python+Kivy编写一个乒乓球游戏

    目录 前言 1.准备 2.简单使用 Kivy 3.Kivy - 添加简单图形 4. Kivy - 增加乒乓球球体 5. kivy - 增加乒乓球体运动 6. Kivy - 球拍移动事件 前言 好久没有写游戏系列教程了,今天恰好浏览到了 Kivy 这个开源跨平台的Python 框架,它能用于开发多点触控的用户界面程序,允许快速简单的交互设计,非常方便,于是有了制作本教程的想法. 本教程将教你如何使用 Kivy 编写一款乒乓球游戏.我们将从一个基本的应用程序开始,描述创建这个游戏的每个步骤. Kiv

  • 详解Nginx proxy_pass的一个/斜杠引发的血案

    背景 一个nginx的server模块下需要proxy到两个server,所以就通过location的不同路径来区分转发到不同的服务器上. 一开始是这么写的 location / { proxy_pass http://server1/; } location /index { proxy_pass http://server2/; } 但是忘记了server1上有个服务路径是/indexNew,结果就被proxy到了server1,出现404问题,然后紧急修改配置如下: location /i

  • 如何用 Python 制作一个迷宫游戏

    相信大家都玩过迷宫的游戏,对于简单的迷宫,我们可以一眼就看出通路,但是对于复杂的迷宫,可能要仔细寻找好久,甚至耗费数天,然后可能还要分别从入口和出口两头寻找才能找的到通路,甚至也可能找不到通路. 虽然走迷宫问题对于我们人类来讲比较复杂,但对于计算机来说却是很简单的问题.为什么这样说呢,因为看似复杂实则是有规可循的. 我们可以这么做,携带一根很长的绳子,从入口出发一直走,如果有岔路口就走最左边的岔口,直到走到死胡同或者找到出路.如果是死胡同则退回上一个岔路口,我们称之为岔口 A, 这时进入左边第二

随机推荐