BUAA_OO_Unit4 总结
(一)需求分析
本单元要实现一个UML解析器。具体来说,本单元作业需要继承官方接口UserApi
,在自己实现的MyUserApi
类中实现对应方法。更进一步,本单元作业要在MyUserApi
类的构造方法中实现对UML图形的层次化建模(包含类图、顺序图和状态图三个部分);之后通过官方提供的对应UML元素类实现自己的UML元素类,在各个元素类中完成官方接口定义的查询方法;在最后要实现模型有效性检查类,从而在层次化建模完成后对整体UML图模型进行检查,排除不合法的模型。整体设计架构如下图所示。
(二)整体架构
(三)架构分析
1 层次化建模
由于UML模型图的输入是乱序的,为了保证层次化建模,需要对输入的UML模型进行多轮遍历,具体如下:
Round 1:
-
类图:
UmlClass
,UmlInterface
; -
顺序图:
UmlInteraction
; -
状态图:
UmlStateMachine
;
Round 2:
-
类图:
UmlAttribute
(同属顺序图),UmlOperation
,UmlGeneralization
,UmlInterfaceRealization
,UmlAssociationEnd
; -
顺序图:
UmlLifeline
,UmlEndpoint
; -
状态图:
UmlRegion
;
Round 3:
-
类图:
UmlParameter
,UmlAssociation
; -
顺序图:
UmlMessage
; -
状态图:
UmlState
,UmlPseudostate
,UmlFinalState
;
Round 4:
-
状态图:
UmlTransition
;
Round 5:
-
状态图:
UmlEvent
。
综合层次化要求和代码复杂性后,具体的层次设计主要采取两层结构,包含:
-
MyClass
—MyOperation
-
MyInterface
-
MyInteraction
—MyLifeline
-
MyRegion
—MyState
(MyStateMachine
只充当MyRegion
类的外壳的作用,严格意义上不是一个层次)。
具体的层次化建模封装到了parse*
方法中。
2 查询方法设计步骤
事实上,在一个合理的层次化建模总体模型下(特别考虑到本单元作业对性能几乎没有要求,所以可以使用最简单的代码逻辑),实现每一个接口待实现的查询方法只是时间问题。总体来说包含如下步骤:
-
抛出存在性异常。
在我们建立的层次化模型中,同名同姓的类图/顺序图/状态图元素是完全可能存在的,此时需要抛出所谓的存在性异常(包含不存在异常+存在多个异常)。由于基本上所有方法都需要进行存在性异常的检查,所以将这个过程封装为一个方法,即
MyUserApi
类中的throw*Exception
方法; -
提供查询方法接口。
在
MyUserApi
类中提供方法接口,调用具体类中的查询方法; -
递归下降实现查询方法。
3 模型有效性检查
在本单元的最后一次作业中,我们需要实现模型的有效性检查。实现过程其实和具体的查询方法类似,但是考虑到代码风格的需求,本次作业将有效性检查封装成了一个具体的MyUmlStandardPreCheck
类,并在类中实现相应的静态方法;通过在MyUserApi
中传入具体的模型参数并向MyUserApi
提供调用静态方法的接口,实现两个类的交互。
(四)架构改进想法
在绘制本单元作业的架构图,特别是其中MyUserApi
部分时,我曾一度想放弃罗列所有的方法,因为类中方法太多了。但是仔细想想,完整的绘制或许可以提醒我一个基本事实:不合理的架构设计会导致代码臃肿。事实上,本单元的架构完全可以有进一步的改进,主要包含以下方面:
-
建模与查询相分离。本单元作业的建模和查询事实上是基本独立开的,完全可以另外实现一个建模类进行层次化建模,实现建模方法的封装;
-
设计类的组合、继承关系。在本次作业中,我主要使用组合+方法接口的方式实现具体查询方法,这就要求在
MyUserApi
类中要提供具体的方法接口,还是会导致方法过多的问题。事实上,官方接口已经对此做出提示:可以划分类图/顺序图/状态图的不同查询需求,通过继承关系统一联系到MyUserApi
中。
二、架构设计思维与OO方法理解演进
(一)层次化建模
在第一单元中,我们通过表达式解析任务初步了解OO的基础——类的封装性。具体来说,我们通过对每个表达式层次对象进行封装,设计具体的成员属性和成员方法,最终实现了表达式解析任务。
再回过头来看我的代码结构,不能说不好,但是也不能说完全符合面向对象要求,例如实现接口还是设计类的问题。但是在设计过程中,我确实尝试采用荣文戈老师所说:“假设你有很多程序员,将任务分出去”的想法,这样的想法带给我的好处就是任务分得清,降低了整体代码的耦合度,这确实降低了我debug的难度。
(二)多线程设计
在第二单元中,我们通过多线程电梯调度的任务初步了解了Java多线程的知识。具体来说,我们通过继承Thread
类来实现线程,通过synchronized
关键字保证线程的互斥,并且通过wait
-notify
方法对实现线程的同步。
在整体架构的设计上,本单元我们接触了生产者-消费者模型,实现了基本的线程间信息的传递;在架构设计上我还采用了组合等类的联系,使得整体架构更加便于管理。
(三)规格化设计
在第三单元中,我们通过社交图网络的任务初步了解了JML规格设计的知识。具体来说,我们通过官方提供的方法接口及其JML描述,实现自己的类与方法,总体来说是一个阅读契约-理解契约-实现契约的整体过程,这也在OO方法上给了我更高层次的提示——设计并不仅仅是类之间功能的协作,更是需求同实现的协作,这里的协作是需要严格规约的。
在架构设计上,本单元的作业我尽力将架构封装为实际结构层-抽象结构层-算法层三个层次,并在设计过程中尽力实现软件设计原则(如开闭原则、单一职责原则、里氏替换原则)等,使得架构设计思想向更抽象的层次进一步。
(四)面向对象基础——UML
最后一单元,我们通过UML解析器的实现回顾面向对象最重要描述语言——UML的相关知识。总的来说,这和第一单元涉及的层次化设计思想是类似的,但不同于第一单元的是,我已经可以灵活掌握继承、组合、关联等类的不同关系,从而实现更加合理的架构。这也许就是架构思维提升的体现。
三、测试理解与实践演进
测试是OO课程中一个重要的环节。在四个单元OO的学习过程中,我实现了不测试—测试调试—手动构造数据的演进,一步步加深了对程序正确性的保证。
第一单元与第二单元的第一次作业中,我基本上采取鸵鸟策略,由于本身对Java语言就不是很熟悉,完成作业已经使我有些心力交猝,所以就不测试,逃避测试,等待强测与互测错了再修bug。直到第二单元的第一次作业,一个小小的for循环迭代的细节让我挂了几乎八成的测试点,我意识到鸵鸟策略或许是不应该的。所以第二步,我开始漫长的合作测试道路(但是其实是单方面的合作,即使用他人的评测机跑自己的程序,然后根据错误调试)。尽管测试的自主性有待质疑,但是不得不说,我在一步步debug的过程中逐渐锻炼了自己定位错误与修正的能力,程序的正确性有了较高的保障。但是老话说得好,打铁还需自身硬,在第三单元中,我面向性能测试尝试进行手动构造数据,并且在三次作业的互测中都进行了成功hack。这其中的喜悦确实是不一般的。
但是我也深知,对于自动化测试我还基本未涉足,正好暑假有Python课程,看看能不能在语言学习后尝试自主构造测评机,以弥补测试中这巨大的遗憾。
四、课程收获
总的来说,OO给我的收获是很多的,包括但不限于以下内容:
-
Java基础语法;
-
多线程编程知识;
-
JML规格化语言的阅读和撰写;
-
UML类图、顺序图和状态图的相关知识;
-
软件设计六大基本原则;
-
单例模式、策略模式等设计模式;
-
层次化分析等架构设计思想;
-
若干不眠之夜与对身体健康的反思(bushi
-
...
回首整门课,可以说是在暂时上的课中对我代码能力提升作用最大的一节课,希望我能够将这些内容好好消化吸收,用到未来自己的其他科目学习和工作当中。
五、课程改进建议
-
预习课程可以适当分配课时(例如两周,可以把JML和UML的课时适当压缩),同时设置一定分值,push同学去完成预习课程,从而更好进行正课学习;
-
可以在指导书中多一些设计模式的提示;
-