C#程序设计分析之俄罗斯方块

来源:互联网 发布:linux zookeeper 安装 编辑:程序博客网 时间:2024/06/05 15:32

前言:

           设计俄罗斯方块程序非常考验程序设计能力,也是学习编程的一个非常好的实践案例。能用设计出这个程序说明对C#语法的掌握基本没有问题了,但是不一定就说明编程能力就很强。我们应该追求的是良好的设计,而不仅仅是可运行的程序, 聪明的程序员应该设计出易读性的代码。我就以这个程序为案例,介绍一下我认为合理的设计方法,牵引大家的视角从学习”语言”转移到学习”设计”上来。

     

      设计要点:

            1. 类的抽象。根据游戏的内容,可以明显找到3个类:组成方块的积木类、具有不同形状的方块类、游戏地图类。积木与方块是一种组合关系,方块与地图是关联关系。积木类负责定义结构,方块类提供操作方法,地图类负责方块控制。有了良好的预设计,就可以尝试编码了。

           2. 建立继承体系。方块有不同的类型,有不同的组成结构,有不同的旋转规则(正方形的方块不能旋转或不适用旋转算法.),应该提炼出一个继承体系。我看过的绝大多数的代码都没有这样做,这些设计者运用【数组】或【switch语句】来实现不同方块类型的条件分歧。比如,给方块对象一个id表示类型,组成方块的积木根据id从数组中或条件语句中创建,并用if语句处理一些特殊的情况。我一开始也是这样做的,但习惯了面向对象编程之后就不喜欢这样做了。这样的代码不适宜冗余面向对象的体系之中,是一种过程式的代码,易读性和易复用性是比较差得。这样的过程式代码让我在阅读的过程中感觉很紧张,逻辑高度的【关联】,往往需要通读代码才能理解程序,任何一处编程细节对程序来说都有致命的影响,自然修改就十分困难。

           3. 设计抽象方法。提取一个方块基类,定义所有不同形状方块的【共性】,并派生出不同的子类表示不同的方块类型(传统的游戏有7中类,那么就有7个子类)。设计抽象方法,让子类通过覆写方法实现不同的形状分歧和旋转算法分歧。举例:设计一个初始化的抽象方法用来创建不同的方块形状,并在基类构造函数中调用。子类只需覆写此方法就可以实现不同的形状。

           4. 用结构体存储坐标。方块的移动是运作在直角坐标系统下的,我们应该用一个合适的结构体类型存储坐标。X、Y坐标不要分开定义成两个int字段,这样对语义会造成很大的伤害。System.Drawing命名空间有一个Point类型的结构体,可以使用它存储坐标。这里选用结构体而非类,主要出于防止别名问题的考虑,结构体是值类型,只以副本进行传递,不用担心内部的数据被无意改变。为了防止游戏的坐标系统与界面的像素坐标系统混淆,可以自定义一个Coord结构体表示坐标。row表示行,col表示列,这样就非常明确了,在对数组索引的时候不会在x、y上产生混淆。

5. 用list列表存储积木。用数组也行,我感觉list泛型更好。这样隐藏了积木存储的【顺序】,在创建积木的过程中不用考虑顺序和数据结构的问题。

           6. 移动算法的抽象。方块可以向下移动、向左移动、向右移动,但不需要写三个方法来实现。移动方向的差别在于位移的差别,不至于扩大到方法的差别。可以在coord结构体中定义静态成员表示不同方向对应的位移,在方法中直接取用即可。实际的坐标计算交给坐标对象,移动方法只管移动逻辑就可以了。一个观点:类应该负责它应该负责的职务,方法应该做它应该做的事情,不能多此一举、多管闲事。

           7. 副本的使用。移动指令的逻辑是:如果可以移动,就移动,否则保持不变。如何移动不变多说,怎么判断是否可以移动呢?我的做法是,先移动一下,看看移动后的坐标所在的位置是否已存在积木,如果所有新的坐标位置都没有障碍,就可以真正移动了。这要怎么实现呢?做移动检验的时候,绝对不能改变方块的状态,这是正确性的根本保证。因此应该创建一个方块的副本,对副本发送移动请求(这里用发送请求一词,表明算法的实现是在其他方法中),改变副本方块的坐标不影响源方块的坐标。

           8. 合适的委托。正如上面所说,方法应该负责它应该负责的部分,它不应负责或不适宜负责的部分可以通过委托将请求递交给具体实现者。举例:在检查移动时(这个方块应该属于方块类)需要判断一个坐标位置是否存在其他积木,不存在积木就称为无障碍的。这个检查方法应该是地图类应该做的事情,应该委托地图类的IsEmpty方法判断某个位置是否为空。这样方块类的移动方法不需要在意什么情况会导致不为空,分工明确使代码的可读性和可维护性增强。

      

0 0
原创粉丝点击