Problem1: Poetic Walks
内容简述
在这个问题中,我们要设计一个ADT——Graph
。它类似于Java开发者给我们提供的通用的容器,比如List
,Map
,Set
。
如果不将具体表现暴露给用户,则我们称之为数据抽象。就像List的具体实现,我们并不知道,除非查Java文档。但是不管它怎么实现的,我们也并不在意,我们只需要在一List提供给我们的功能就可以了。
我们知道,List
是一个继承与Collection
接口的一个子接口,List
下边有很多类实现了他,构成了不同的集合,如LinkedList
、ArrayList
等。我们要实现的Graph
也是一个接口,有两个具体类等着我们来实现,就是ConcreteEdgesGraph
类和ConcreteVerticesGraph
类,这就是我们这个实验的Part1。
由于一切的软件开发都要遵循测试优先原则,我们在Part1之前,还会有一个Part0,就是写测试。
在这之前,我们是实现了两个具体类,这两个具体类的label
都一定是String
。在Part2,我们要来实现从String
到泛型<L>
的转变。
Part3 是应用我们写的Graph,完成自动写诗。
Part0 Testing
我们设计ADT的顺序应该是 Spec$\rightarrow$Test$\rightarrow$coding。
Spec相当于coder和user的规约,只有定好了规则,user按照规则不输入不合法输入,coder按照规则给出正确的输出,才能完成设计,所以Spec是要第一个完成的。(虽然写Spec很让人头大)
而在Graph接口中,Instructor已经给了我们Spec,因此,在知道这些Spec的前提下,我们就可以来编写Test了——尽管没有具体的实现,这是一个彻头彻尾的黑盒测试。
测试的编写要遵循分区测试原则。
我们把测试数据根据不同类型的输入来分区,在这里就不讲分区了,可以参见软构课堂笔记。
每个测试前要加上测试策略的注释,说明你为什么这么分区,如:
1 | /* |
Part1 Implement Graph<String>
将泛型L考虑为String,并用两种不同的数据结构来实现。这一步很简单的,虽然你会有错觉这才是实验的主体。不过这真的只是最简单的一环。
事实上,在实现之前我们要写Spec,不过由于接口中各个方法的Spec都已经写好了,所以这一步不需要我们去做。
每个类都要写RI/AF/Safety from rep exposure,其具体的含义可以这么理解:
RI是你定义的合法的field。
AF是你定义的从表示空间R到抽象空间A的一个映射关系。
Safety from rep exposure 是说明一下你这个class为什么没有表示暴露。
像这样:
1 | // Rep invariant: |
Part2 Implement generic Graph<L>
将String换为泛型L,具体方法MIT的instruction里都写的很清楚了。
Part3 Poetic walks
在这一步,是对我们写的ADT的简单的应用,注意依旧要遵循Spec$\rightarrow$Test$\rightarrow$coding的顺序来写。
翻译一下题目描述吧。
对于一个诗集,我们忽略大小写,将相邻的两个单词(包含标点)前一个向后一个连一条边,边权为1;如果已经有边了,那么边权+1,。然后我们个你一个简单的句子,让你扩充成一首诗。扩充原则是两个相邻的单词A B间加上C,W(A, C) + W(C, B)最大。如果不存在就不添加。
实现不太难实现。
Problem2: Re-implement the Social Network in Lab1
这似乎也没什么好讲的,注意L是immutable的,所以我们放一个名字就好了。
Problem3: Playing Chess
这一阶段我们要从零设计一个ADT。
我就按照我的实现过程写一下吧,我没有完全按照Spec$\rightarrow$Test$\rightarrow$coding这个步骤来,下个实验一定要改正这个问题,感觉写之前想好确实会省掉很多麻烦。
我是在写之前想了一半:想了好了具体的类的功能,类中的field和method,但是没有写Spec和Test。这就导致有的时候我没有写着写着不知道这个类是干嘛的了,同时这是mutable还是immutable class有的时候也会忘记。你以为你用脑子就能记住,其实还是写成Spec好。
具体的定义脑补一下就可以了(其实是我懒了)。
另外,个人认为我的Player
类的设计是个败笔:
Player
本来只需要定义一个名字,定义一个棋子的颜色黑/白,两个String
,那么它可以设计成immutable类。不过实验最后有个要求要打印棋谱,我就讲Player里加了一个List
。那么没move一次,就要在这个list中add一次。这样有一个问题就是它无法定义为immutable类了。而在其他类中,我们要无数次的调用Player
类型的变量player
,那么就要防御是拷贝。然而我们还想修改player
里的list,但是我们根本无法触碰到player,只能触碰到他的拷贝后的副本。于是我们又要创建一个复制list的方法。
如此的麻烦,我们还不如将list放在Player
外来实现,会简单很多。