软件构造实验总结

Lab1

现在做完三个实验再回头看Lab1,发现真是基本没干什么,只是简单地熟悉了一下Java编程。

P1:Magic Squares

我遇到的问题主要是要实现从文本中读入数据并进行合法性判断的功能。通过上网查阅资料,最终学会了读操作,需要用到FileReader和BufferedReader。这是第一次的上网自学,当时还觉得挺了不起的,后来发现在编程过程中上网自学一些Java中的有关知识简直就是家常便饭。

后续的数据操作此处不再描述,我当时的代码风格还是C的风格,比如使用大量一维数组、二维数组进行遍历查找,但在后面的实验中有逐渐改进,慢慢地习惯了使用List、使用迭代器等等。

P2:Turtle Graphics

这个任务是我第一次接触到的填空式的程序设计,形式很新颖,趣味性较强。

任务要求主要就是参照给出的spec完成几个方法的实现,使得已有测试用例能通过(这也是在此门课程中首次接触到Junit测试)。每个待实现的方法其实都是一些简单的算法。值得一提的是一个求解凸包的问题,我的大致思路是先找到所有点当中的最左下角的点,然后遍历其他的点,找到能使得turtle旋转角度最小的点,移动到该点上,再用相同方法继续查找下一个点,以此类推,直到回到一开始选定的最左下角的点,说明已经找到了所有点当中在最外圈的点,也就得到了凸包。

当我把一个到处是空白的框架逐渐丰满起来,测试用例也一个个通过的时候,还是很欣慰很有成就感的。

P3:Social Network

这个任务要求设计实现一个FriendshipGraph类来表示现实生活中的社交关系图,还要设计一个Person类来表示人,最后还要自己编写测试用例来对FriendshipGraph中的方法进行测试。当时觉得做了好多的工作,现在回头看只想一笑而过。

在这个任务的完成过程中,我对ADT以及TDD有了一个最最基本的了解(虽然这时候还不知道这些名词),初次感受了一个比较完整的简易的程序设计流程。至于完整的设计流程,在Lab2和Lab3中逐渐揭开了神秘的面纱。

Lab2

从Lab2开始,总算是接触到这门课程的核心了——ADT和OOP,实验难度也有了明显提升。

P1:Poetic Walks

其实P1可以被分成两个部分,第一个部分是设计一套ADT,第二个部分是利用这个设计好的ADT来完成一个应用,也就是poetic walks。

这个任务和Lab1的P2一样,也是填空,不过这次填的东西就多了。查看已经给出的Graph类,里面声明了一些方法,通过对每个方法的spec进行阅读,确定首先要做的就是划分等价类、写测试策略并设计测试用例。这其实就是TDD的一个体现,以前的编程中我都是直接完成实现,在这里按照要求应该先完成测试用例,这样能使方法在实现之后能立即得到测试,确保其正确性及健壮性,而且在设计测试用例的过程中,我们也能提前考虑各种特殊情况,在完成实现的时候加以注意。

完成了测试用例,接下来就该实现Graph了。任务中要求了两种实现方式ConcreteEdgesGraph和ConcreteVerticesGraph,这就是对OOP中继承关系的体现了。两个类都是对抽象接口Graph的实现,只是两者的内部逻辑不一样,但对外表现出来的都是一样的,都能提供Graph中的方法。两者的测试用例也是一样的,都继承自Graph的测试用例,因为实现了相同的方法。

在实现过程中,还涉及到了mutability/immutability说明、AF、RI、safety from rep exposure以及每个方法的spec,这些内容部分已经给出,但绝大部分仍需要自行添加完善。这些说明能使代码具有更加清晰的逻辑,既能帮助别人读懂自己的代码,又能方便日后查看代码时快速明白当初的想法,还能提醒自己时刻注意所写程序的正确性、安全性,保证不会在以后被客户端恶意操作,同时也可以清楚展示给客户端各个方法能实现什么功能,以及该怎么输入。

接下来就进入了P1的第二个部分,应用ADT实现一个poetic walks的功能。还是一样的,先阅读spec了解清楚各个方法要实现的功能,设计相关的测试用例,然后再实现各方法,完善mutability/immutability说明、AF、RI、safety from rep exposure以及每个方法的spec。在实现各个方法的时候,由于已经设计好了ADT,所以能直接调用其中的方法,省去了很多工作。

P2:Reimplement Social Network

这个任务其实和P1的第二部分是一样的,就是用已设计的ADT来完成一个应用的实现,而这个实现正是在Lab1中完成过的社交关系图。有了Graph这个强有力的ADT,很快就能实现FriendshipGraph这个类。同样,mutability说明、AF、RI、safety from rep exposure以及每个方法的spec也是不可省略的。虽然这里只需要把Lab1的测试用例直接拿来用就行,但为了按照更标准的模板来,我还是划分等价类、写测试策略并设计测试用例,使得测试更加充分。

到此,我在Lab2中初次体验了面向ADT的编程。不同于以往的直接面向应用场景的编程,面向ADT的编程是一种具有更长远眼光的做法,能在以后更加方便的实现更多具有相似性的应用;面向应用场景的编程虽然在短期看来很方便,但是可扩展性差,在遇到相似情景的时候,只能做大量重复工作,将相似的功能进行反复开发,效率较低。

Lab3

到了Lab3,就是整个三次实验的最高潮了,除了一份实验手册上的思路介绍,所有的东西都要自己设计与实现,是真正的从零开始。

在Lab3里,要求实现三个应用——排班管理系统、操作系统进程调度管理系统和课表管理系统。对这三个应用进行分析,能发现它们均涉及到具有不同特征的“时间段集合”这种对象,为了提高软件的可复用性和可维护性,可以设计构造一套ADT。

一开始我还比较迷茫,就参照着Lab2中设计Graph的那一套流程来进行。首先是设计IntervalSet,在实验手册的提示下,我确定了IntervalSet这个抽象接口中的方法,并完成了每个方法的spec。然后划分等价类、写测试策略并设计测试用例。接下来对IntervalSet这个接口进行实现,不同于Lab2的是,这里只用完成一个实现,即CommonIntervalSet。mutability说明、AF、RI和safety from rep exposure也是需要完成的。这样做下来,我也渐渐明白了设计流程。

在IntervalSet的spec里面,提到里面的时间段的“标签”是不能重复的,这在排班管理系统里适用,但在操作系统进程调度管理系统和课表管理系统里就不适用了。于是,我们还要设计实现另一种“时间段集合”MultiIntervalSet,它允许不同时间段的标签“重复”。在实验里,我选择将其直接实现为具体的类,而没有设计相关的接口。和IntervalSet一样,MultiIntervalSet各个方法的设计与spec撰写、测试策略、测试用例、方法的具体实现、mutability说明、AF、RI和safety from rep exposure都得按顺序逐一实现。

接下来就要对局部共性进行设计与实现了,这里将用到设计模式的相关知识,我最终选择的是装饰器模式。对于三个维度的特性——是否允许空白、是否允许重叠及是否需要周期性重复,将它们分别看做一层层装饰,逐一地加到原本的对象上。由于排班管理系统会基于IntervalSet来实现,而它要求不允许空白和不允许重叠的特性,我就为之设计了两个用来装饰的类NoBlankIntervalSet和NonOverlapIntervalSet,它们都继承同一个类IntervalSetDecorator,而这个类是对接口IntervalSet的一个实现。然后再将NoBlankIntervalSet和NonOverlapIntervalSet逐层装饰到IntervalSet上,就得到了用于排班管理系统的DutyIntervalSet。

需要注意的是,NoBlankIntervalSet中添加了原IntervalSet中没有的方法checkNoBlank,为了能调用这个方法,在装饰的时候需要把它放在最外面的一层,并将对象声明为NoBlankIntervalSet而非IntervalSet。这样看来似乎有些打破装饰器模式,但我在查阅资料时发现这被称为半透明的装饰器模式,介于装饰器模式和适配器模式之间,允许对接口进行改变以增强功能。

类似的思路,继续为基于MultiIntervalSet的操作系统进程调度管理系统和课表管理系统设计MultiIntervalSetDecorator类,注意它并不是实现MultiIntervalSet,因为MultiIntervalSet已经是具体的类,而非抽象接口。这样似乎又违背了装饰器模式,但是其思路是完全一致的。再设计并实现NonOverlapMultiIntervalSet、OverlapMultiIntervalSet和PeriodicMultiIntervalSet,并通过包装得到用于操作系统进程调度管理系统的ProcessIntervalSet和用于课表管理系统的CourseIntervalSet。同NoBlankIntervalSet一样,PeriodicMultiIntervalSet也属于半透明的装饰器模式。

设计完三个面向应用的ADT,再追加三个immutable的类Employee、Process和Course,以及自主设计的API,我们的基础框架就整个完成了。运用这一套自主设计的ADT,我们已经能解决不少现实中的问题了。

接下来的工作就是用已经开发好的ADT来完成一开始提到的三个应用——DutyRosterApp、ProcessScheduleApp和CourseScheduleApp。在已有的ADT的帮助下,每个应用中要求的功能都比较容易想到解决方案。几个小的问题,例如对日期的计算、随机执行时间的“随机”的实现等,在自学LocalDate和Random后都能很快解决。比较大的问题就是对于各种情况的分类讨论不够全面,总是会出现一些漏洞,导致手动测试的时候bug的花样繁多。我也在这段修改代码的长时间斗争中感受到了数学基础和算法设计的重要性,以后还得多多加强这方面的训练。

实现完三个应用之后,还需要给排班表新增一个从文件读入初始化数据的功能,这不仅需要会读文件的操作,还要学会正则表达式的写法。于是我又继续上网找代码示例自学(不得不说自学能力真的太重要了!),参照着代码示例写出员工信息、排班信息和排班时间的正则表达式后,运用Pattern和Matcher,以正则表达式为模板,在文件输入里面进行匹配,获取需要的信息。一旦获取到信息,后续的初始化就很容易了。

好不容易完成所有的功能,又要面临新的变换——排班管理系统要允许一个员工多时间的排班,课程管理系统不允许任何课程的重叠。但是还好我们是面向ADT的编程,在面对这些变化时是比较容易的,不用大量重复的工作,只需少量的修改。

排班管理系统的变化,只需要新增一个NoBlankMultiIntervalSet类来继承MultiIntervalSetDecorator,再修改DutyIntervalSet,将其变成用NoBlankMultiIntervalSet和NonOverlapMultiIntervalSet装饰的MultiIntervalSet,最后修改DutyRosterApp中利用IntervalSet特性实现的少量代码即可。

课表管理系统的变化更加简单,甚至不用新增类,只需修改CourseIntervalSet即可,将其装饰由OverlapMultiIntervalSet改变为NonOverlapMultiIntervalSet就完成了,几乎不用花时间。

到此,软件构造的三个实验就都圆满结束了。我从一个毫无设计思维、只会一来就实现各种方法的小白,成长为了一个初步具有设计意识与习惯的软件构造初学者。期间,我体会了ADT与OOP的魅力,感受了面向ADT编程带来的良好的可维护性和可复用性,当然也经历了各种困难与崩溃。但是最终回顾整个过程时,收获颇丰,成就感十足,内心还是十分喜悦与欣慰的。

关于Git

在三次实验中,提交成果均用到了git,下面记录一下用到的几个指令:

git init  创建一个本地git仓库

git clone xxxx 克隆一个仓库中的代码

git remote add origin xxxx 将本地仓库与远程仓库关联

git remote -v 查看远程仓库相关信息

git add (filename) 将工作区内容交到暂存区

git commit -m "xxxx" 将暂存区内容提交到本地仓库

git status 查看仓库状态

git push -u origin master 将本地仓库内容提交到远程仓库上

git checkout -b change 创建新的分支change

git checkout master 切换回master分支

关于在线build

我使用的是GitHub上的在线build,在Actions目录下选择Java with Maven:

选择后会自动生成maven.yml文件,将其中版本修改为指定版本(我用的jdk8)再提交即可:

build的结果如下:

注意在build之前要先提交一个pom.xml文件,否则可能会失败,文件内容如下(其中artifactId需根据每个仓库的仓库名进行修改):

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值