论如何从零开始攻略一个ADT——ADT整体设计篇

  一些自己在做Lab开发ADT时的经验和体会。

  没啥理论上的内容,大家当乐子看看就好(:

  本文主要介绍如何根据需求,来决定要设计哪些ADT,以及如何处理不同ADT之间的包含关系。

 

1.ADT的选取

  如果甲方已经规定了需要实现哪些ADT,那自然是坠吼的。

  如果没有,首先我们需要分析甲方的需求,然后提取出其中有价值的实词

  以某实验为例:

  棋类游戏由一个棋盘、一组棋子组成,双方交替在棋盘上走棋。

  一个尺寸为 n*n 的棋盘:其中 n 表示每一行/每一列的格子数。

  棋盘上共有 n*n 个格子和(n+1)*(n+1)个交叉点

  一组棋子:每个棋子属于一个特定的种类,不同的棋类游戏中包含的棋子种类不同。

  在一盘棋局中,不同种类的棋子的数量不同。

  在游戏进行过程中,需要区分棋子是属于游戏中的哪一方玩家

  要求的实词已用粗体标出,最简单粗暴的方法,就是对每个实词,都设计一个ADT与之对应

  这样的好处是无脑,维护起来方便,而且可扩展性强:除非甲方一拍脑袋决定在项目中新创造一个概念,不然操作都是基于已有的概念。

  麻烦就是ADT的嵌套的深度会很大,而且在后面考虑不同ADT之间关系的时候会很麻烦。

  一种方法是根据经验,来去掉不必要的ADT,例如上例中的尺寸、种类、数量。

  由于JAVA本身对对象丰富的支持,大多数操作完全可以靠已有的方法来实现,像是尺寸、数量这种单纯的Integer,或者种类这种单纯的String,而且这些ADT在将来的应用中,扩展的可能性并不大。

  假如说这些ADT并不“单纯”:例如一个种类实际上由一个字符串和一个整型编码构成,或者将来可能会有扩展:例如玩家这个ADT,将来可能会包含拥有的棋子种类之类的信息,那么就不建议省略掉了。

  此外,像是种类这个属性,在JAVA里可以省略成一个String,但在C里,还是老老实实地去写个struct吧。

  另一种方法是合并实现上类似的ADT,例如玩家和棋子,它俩在需求中都是只有一个Sting型的label/name,但是又由于将来可能的扩展,而不便于去掉。

  因此我们可以定义一个item类,里面仅有一个String型的label,然后将玩家和棋子都声明成item类。

  当然这样也有不少麻烦,比如未来不便于特化扩展——这一点可以用继承来克服——以及由于item类里的方法命名都比较一般化,很容易在实现的时候乱套:player.getLabel()是指玩家的名字还是玩家的编号,还是别的什么?

  所以我个人感觉比起在第一次编写的时候就进行合并,还是在将来用到的时候想起来,以前写过一个类似的比较合适。

 

2.ADT的关系

  这里主要讨论字面意义上的“属于”这层关系的处理。

  显然棋盘和玩家两者之间是没有关系的:玩家不属于棋盘、棋盘也不属于玩家。所以这两个可以大大方方地视作并列。

  而对于游戏、玩家、棋子、坐标这四者,直觉上认为是递进的包含关系,坐标∈棋子∈玩家∈游戏。但去分析一下需要实现的操作,就发现这样是不合适的:我们需要根据跟定地坐标,来确定在这里的棋子;需要根据棋子,来确定它属于哪个玩家。

  当然我们可以大大咧咧地去把所有棋子遍历一边,来找出哪个棋子的坐标是给定的坐标。这种方法好在直观,坏处也是一堆,就不在此赘述了。

  从上述讨论可以看出,问题在于我们的检索并不是单向的,而是双向的,所以虽然直觉上它们是属于关系,但在操作上,它们的地位是对等的。

  所以有一种解决方案是:完全抛弃属于的概念,而将二者并列。也就是说,{坐标,棋子,玩家}⊆游戏,但是坐标、棋子、玩家三者并列。

  但没有了属于这层关系,我们怎么确定谁跟谁对应?

  方法很简单:我们用Map映射表来记录这个对应关系,由于Map中实现了键值互查的方法,所以我们可以很方便地进行双向检索。

  以下述代码为例:

public class Game {

    private final String type;
    private final Board board;
    private final List<Piece> pieces;
    private final List<Position> positions;
    private final Player player1, player2;
    private final Map<Piece, Player> belong;

}

  可以看到,棋盘、棋子、玩家、位置是并列关系,而棋子和玩家的对应关系记录在belong中。

  棋子和位置的关系我记录在了board的一个子Map中,为了体现这种对应关系是在棋盘上的。

  而由于我们并不需要实现:确定某个玩家是在哪个游戏里,或者某个棋子是在哪个游戏里这种操作,所以我让棋盘、棋子、玩家、位置都属于Game这个类。

  现在看来,这种关系可以进一步“真实化”。

  例如,positions应该放在board当中:我们并不需要根据位置确定棋盘;棋子和位置依然是一定程度上的并列关系;这样看起来更真实。

  此外,这两种方法在效率上应该是一致的,因此第二种方法更像是增大空间开销,来减少方法的设计,从而减少潜在的bug。

 

    

 

posted @ 2020-03-28 14:02  WDZRMPCBIT  阅读(535)  评论(0编辑  收藏  举报