这次作业主要是实现一个UML类图分析器,并通过输入各种指令来进行类图有关信息的查询。
为了更好地管理和查询数据,首先在官方包给的UmlClass
、UmlInterface
和UmlOperation
的基础上重新进行封装,实现相应的类MyClass
、MyInterface
和MyOperation
。这样的话,比如在MyClass
就可以更方便的管理与这个Class有关的数据,比如该类包含的属性、操作,该类实现的接口,该类继承的类和该类关联的类或接口等等,同时也可以在类内实现相应的方法来进行查询等操作。
关于元素的实例化,可能出现类似以下情况:要实例化一个继承关系,但是可能该继承关系的父类或子类还没有实例化。所以,我采用的策略是先将可以不用依赖其他对象的元素实例化,比如UmlClass
和UmlInterface
可以直接实例化,其他的UmlElement
先存到各自所属类型的List
里,等待所有元素都遍历一遍后,再统一按元素类型进行处理。
其他要做的就是通过选择合适的容器来存储对应的数据,此处便不再赘述。
第二次作业则是在类图解析器的基础上进行扩展,支持对状态图和顺序图的分析,可以通过输入相应的指令来进行相关查询。
关于状态图的分析,我重新封装了MyStateMachine
和MyRegion
。其中,MyRegion
就像是状态机的画布,在MyRegion
中,选择合适的容器记录了初始状态、最终状态和其他状态,也记录所有状态间的迁移。其中,需要查询一个状态后有几个后继状态,我采用的是floyd算法,将所有状态的邻接矩阵转换成所有状态的可达性矩阵,可以比较方便地得到所有状态的后继状态。
关于顺序图的分析,相对比较简单,我重新封装了MyInteraction
,只要用合适的容器记录消息、生命线和参与对象等,就比较容易地可以查询到要求的内容。
第三次作业是要在前两次作业的基础上对模型进行有效性检查。
关于“针对下面给定的模型元素容器,不能含有重名的成员”,我的策略是在封装的MyClass
类中增加了一个umlRule001Set
,再加入attribute
和associationEnd
时,就先判断各自内部是否存在重名的现象,如果有就直接加入umlRule001Set
。最后再检查associationEnd
和attribute
中是否存在同名的现象。
关于“不能有循环继承”和“任何一个类或接口不能重复继承另外一个类或接口”,我的策略是重新构建一个类MyClassOrInterfaceGraph
,首先将所有的类或接口加入图中,然后将继承关系加入图中。在MyClassOrInterfaceGraph
中,我们将继承关系看做一条有向路径,并且构建一个邻接矩阵,将路径加入邻接矩阵。然后通过floyd算法将邻接矩阵变成可达性矩阵,最后如果一个类或接口有到达自己的路径,说明存在循环继承;在构建一条新路径的时候,发现已存在路径,说明存在重复继承的现象。
关于“任何一个类不能重复实现同一个接口”,我的策略是在原先MyClass
和MyInterface
的基础上做一点改动,要得到所有实现接口的列表,看是否存在重复id的接口。
关于“类图元素不能为空”,我的策略是在将这些元素实例化时就判断其name
是否为空,除了UmlAttribute
可能属于顺序图,所以需要判断其是否是类图元素后再做判断。
剩下的判断较为简单,此处便不再赘述。
第一单元应该说是我对架构设计的理解变化最大的,第一、二次作业几乎仍然是按照面向过程的思维来写代码的,甚至试图在主类中完成所有的工作,但是到了第三次作业难度的陡升,我意识到了这样是不可行的。所以我在第三次作业进行了重构,列出了多项式中可能出现哪些元素,并对所有的元素进行抽象,提取出共性,开始简单的使用继承等方法,减少类之间的耦合,但是对于架构设计的理解还是不够到位。
第二单元引入了多线程,应该说让我理解到了设计模式的重要性。如果有一个好的设计模式,那么对于程序的设计应该说大有益处。虽然我只是用了简单的生产者-消费者模式,但应该说生产者-消费者模式对于解决多线程的问题是很有用的,可以避免多个线程在访问共享资源时的冲突,而且我每次的作业不用进行重构,只需在前一次作业的基础上进行迭代,大大减少了工作量,也不容易出错。
第三单元是面向JML规格化的设计,学习到了一种契约式的编程思想,如何根据源代码中的JML规格,来实现官方包接口中的方法。课程组提供的接口设计,也让我再次感受到了一个清晰的架构设计的重要性,在实现方法时不必担心会不会和其他方法冲突或重复,功能明确。
第四单元则是关于UML统一建模语言,经过前三单元的学习,应该说我已经在掌握了一些简单的架构设计的方法,所以在开始写代码前,我就设计好了要重新封装的类,根据类图、状态图、顺序图的架构来进行架构设计,这可以使我很容易地进行管理和查询数据。
在第一单元中,我对于测试并不是十分重视,这也导致了我在第一次作业和第三次作业的强测中都出现了问题。后来在bug修复的过程中,我发现都是很细微的问题,这也正是我测试的时候只是简单测了几组数据,对于特殊情况和边界情况的测试太疏忽了。
第二单元的测试我就吸取了第一单元的教训,针对一些极端数据和特殊情况的数据手动构造了一些测试集进行测试,比如在第三次作业,对于各种换乘情况,我都构造了样例进行测试,这也使我及时发现了自己的错误,并修正了。同时,我还通过随机生成数据来进行测试,所以我也顺利地通过了第二单元的强测。
第三单元由于是面向JML规格进行实现方法,所以不大容易出错,测试较为简单。但是在这一单元中,我学到了一种很重要的测试方法——单元测试,它可以将一个方法作为一个单元从整个程序中分离出来进行测试,这是在工程实践中保障程序正确性的很重要的方法,通过实验的训练,我也掌握了一些单元测试的方法。当然我也接触了JUnitNG自动生成测试用例的工具,虽然效果不是很好。
第四单元测试其实比较难进行,但是很重要的一点就是在写代码和检查代码的时候,要考虑到一些极端情况,并注重思考代码和图之间的关联,做好架构设计。
我觉得这个课程最大的收获就是加深了面向对象这种程序设计思维的理解。老实说,在之前的数据结构用Python写代码时,我对“面向对象”这个概念的理解是很模糊的,尤其是一开始完全把类中的方法理解成“面向过程”的函数。而在我学习了“面向对象设计与构造”这门课程后,回过头去看Python,应该说解决了很多当时遗留下来的疑惑。在学习这门课程的过程,应该说我不断在体会和感受着“一切皆对象”的思想,哪怕是生活中任何一个物体,我们都可以以“面向对象”的思想去看待它,比如它管理哪些数据、它可以实现哪些功能、它的抽象是什么等等,更进一步,“面向对象”的思想让我在看待这个世界的时候都可能会多出一种新的角度(突然玄学qwq)。
抽象也是我认为在“面向对象”的程序设计中很重要的一个思想,我们如何去提取元素之间的共性,并赋予不同元素以个性。抽象的思想有助于我们进行架构的设计,在开始写代码前,一个好的架构设计甚至不能说“事半功倍”,应该说是整个工程的地基,没有架构设计写代码就是天马行空,很有可能遗留下许多的bug。
在学习这门课程的过程中,我还学到了测试的重要性,尤其是“中测-强测-互测”这样的测试机制,更让我感触颇多。有好几次我都是一边提交,中测AC了,就不管了,最后出现强测大面积报错,或者是在夜里被刀了十几刀的惨状,而最后de出来可能只是某个变量名写错的问题,我每次的bug修复都是一次修复所有的bug,但是分数已经扣了很多。这让我深刻地体会到测试的重要性,在未来的工程实践中,任何一个细小的bug都可能导致致命的问题,这也算给我敲响了警钟。在这门课程的学习中,我也学习了如何去构造一些数据(尤其是边界数据),也学习了如何通过单元测试提高代码的正确性。
总之,OO这门课程带给我的收获还是很大很大的,也不是几句话能总结完,只希望在未来的学习和工作中可以将学到的这些东西学以致用。
以下谨代表个人主观意愿,如有考虑不周之处,还望见谅:
个人认为实验课的成绩还是应该发布一下的~
个人希望博客作业的形式可以更加自由一点~
似乎暂时没有别的建议了,那只能提个不合理的要求,希望强测中bug修复可以弥补的分数多一点吧~(虽然强测的确是要考察测试能力,但是每次bug修复都只要改一两行就AC了,真的很受伤[手动笑哭])
这学期虽然是在线上学习OO,但是整体来说感觉效果和线下差别不大,同时线上录播课程,如果有听的不明白的地方,可以暂停,想清楚了再听课;另外,课后还可以回放视频学习,这些都算是线上学习的好处吧。虽然相比于线下,没有了和老师、同学当面交流的机会,但是我们还是有研讨课、课堂讨论等形式,依然有多种方式和同学们交流。整体来说,还是从OO这门课学到了很多东西吧,收获也很大!