面对对象编程 之 第四单元总结

面向对象编程 之 第四单元总结


  • 针对第四单元和本学期所学的内容,在独立思考的前提下在博客园完成技术博客的撰写:

    • (1)总结本单元三次作业的架构设计
    • (2)总结自己在四个单元中架构设计OO方法理解的演进
    • (3)总结自己在四个单元中测试理解与实践的演进
    • (4)总结自己的课程收获
    • (5)立足于自己的体会给课程提三个具体改进建议
    • (6)谈一谈线上学习oo课程的体会



1.架构分析


本单元围绕UML模型实现对UML类图、UML顺序图和UML状态图的解析,并要求对模型进行有效性检查。总体来说,本单元的架构设计并不理想,改进空间仍然很大。


1.1 第一次作业

第一次作业只需要实现对UML类图的解析,即实现MyUmlInteraction接口,实现对类及其属性、操作、继承和可见性等,以及接口的继承和实现等的查询操作。本次作业难度不大,初次设计架构较为简单,只需要一个MyUmlInteraction类实现,并且同时利用多种容器(如HashMap,HashSet等)存储缺乏数据依赖的输入元素;但同时缺点较为明显,没有实现不同功能的分离,实现类代码长度长达400+行,没有很好地遵循OO的设计理念。

(1)MyUmlInteraction

UML类图涉及的查询方法有几类,分别如下:

  1. 未涉及继承的类内查询:ClassCountClassOperationCountClassOperationVisibilityInformationNotHidden。由于查询元素较多,这些无需进行存储,仅仅对于存储的HashMap进行统计相应信息即可。同时,用HashMap存储查询后的缓存以便下次查询(但是此单元作业对于时间的要求不高,所以缓存可能是不必要的)。
  2. 类的继承查询:ClassAttributeVisibilityTopParentClassClassAttributeCountClassAssociationCountClassAssociatedClassList。有关类的继承,与1的区别在于由于Java8不支持类的多继承,所以父类和子类是一对一的关系。进行统计信息时,查询任意一个类的像关系需要不断向上查询其父类相关的信息,直到根才停止。
  3. 接口有关查询:ImplementList。与2不同的是,接口是多继承的,即一个子接口可能有多个接口。处理方法是将接口有关继承看作需要宽度优先遍历的结点,利用非递归BFS查询所有(祖)父类。

对于以上三种查询,笔者利用idToElement存储id到UmlElement的映射,classToXXX存储类的有关信息,inheritClass/inheritInterface存储类和接口的继承信息。强测中因为interface的继承查询操作中的BFS少写了一个已遍历的条件,导致错了一个点。

反思改进

本次作业的架构并不完善,将多个数据结构存储在同一个类中,写起来极容易出错。经过思考后,可以将类和接口单独拎出来,并且对较复杂的可能的改进方法如下:

  1. MyUmlAttribute类:一个类的属性的集合,负责存储一个类里的attribute集合,并实现对其数量和可见性的查询。
  2. MyUmlInterface类:接口,主要存储其fathers以实现接口的查询操作。
  3. MyUmlClass类:类。主要属性有MyUmlAttribute、operations(类的操作)、fathers(父类)、myInterfaceList(实现的接口)、ends(类的对端),在其内实现对类的继承、关联、操作和实现的类等等的查询操作。
  4. MyUmlInteraction:UML类图,主要有MyUmlClassList和MyInterfaceList两个属性。构造函数把乱序的UmlElement分别传递给类和接口,查询方法大大简化。

13


1.2 第二次作业


相对于第一次作业,此次作业还要求实现对UML顺序图和UML状态图的解析。总体架构设计思想仍然不变(因为几乎没有设计…),新增了两个类分别解析顺序图和状态图。MyStateChartInteraction类用来处理状态图的查询,MyUmlCollaboration类实现对顺序图的查询。MyUmlGeneralInteraction盛放了UML三种图的解析类,与外部接口进行交接。

(1)MyUmlClassModelInteraction类

与第一次的MyInteraction类相同。

(2)MyStateChartInteraction类

UML状态图涉及的查询方法较为简单,只有SubsequentStateCountTransitionCountStateCount三个查询方法。其中后两种方法直接对进行信息进行统计即可,SubsequentStateCount负责对后续状态的查询,由于一个状态可能有多个后续,所以此处采用BFS进行查询状态的后续个数。

(3)MyUmlCollaboration类

相较于UML状态图,UML顺序图涉及的查询方法甚至更简单,涉及ParticipantCountMessageCountIncomingMessageCount三个查询,均可以通过查询相关的Map来直接统计相关属性的个数。

总体来说,第二次的架构较为清晰,新增的两个类功能分工明确,代码均在150行左右,并且类内方法耦合度也不高。此次设计所存在的问题仍然是第一次作业遗留的设计问题,即UML状态图的架构设计,详见1.1节的反思改进。


1.3 第三次作业


第三次作业新增了对模型的有效性检查。在第二次作业的基础上,增加了一个类(MyUmlStandardPrecheck),并对顺序图和协作图的实现进行了修改。

(1)MyUmlClassModel类

与第二次相同。

(2)MyStateChartInteraction类

在前一次作业的基础上,此类仅仅增加了checkRule007()checkRule008()方法,用于检测顺序图中初始状态和终止状态的正确性。

(3)MyUmlCollaboration类

与第二次相同。

(4)MyUmlStandardPrecheck类

反思改进:

  1. initial和updateOne的耦合较高,因为构造函数中反复使用。
  2. rule003和rule006出现了冗余if分支,可以修改去除。
  3. 部分代码出现了重复的情况,尤其是rule002和rule003中对于类和接口的检查。应该是没有封装成单一方法所致。


2. 架构设计及OO方法理解的演进


2.1 第一单元

第一单元的三次作业内容主题依据多项式求导依次展开;强调对OO基本思想和工厂设计模式的理解。

工厂设计模式:定义一个创建对象的接口,让其子类自己决定实例化哪一个工厂类,工厂模式使其创建过程延迟到子类进行。其 主要解决 接口选择的问题,在后两次作业中有明显的体现。

第一次作业中,由于仅需要实现普通多项式的计算和化简,设计较为简单;分为负责语法分析的InputParser类和基本函数的Power类,分别负责解析输入字符串和求导运算。严格遵循单一原则,但工厂设计原则体现并不明显。

第二/三次作业的架构遵循了工厂设计原则。负责语法分析的InputParser类、基本函数的抽象类BasicFunction类就是我们的工厂:InputParser类先解析字符串,构建表达式(Expression),项(erm),因子(Factor)三种表达式类型,但并不需要知道表达式内部存储的信息;而BasicFunction类也是一个关于 Factor的工厂,他不关心下面究竟是常数,幂函数或是三角函数,只需要关心BasicFunction需要实现的基本接口(求导, 乘法等等)。


2.2 第二单元

第二单元的三次作业内容主题依据电梯调度依次展开;强调多线程编程,观察者模式和生产者-消费者模型的理解。

第一二次作业均可抽象成一个多生产者-多消费者模型。生产者即乘客请求(Request),唯一的消费者即单部电梯(Elevator)。“托盘”,即调度器(Scheduler),是用来盛放乘客请求并加入到电梯内。调度器本质上负责管理输入线程输入的请求。它是一个由请求队列组成的协调区,只负责乘客请求对应电梯、乘客间排列的规划,完成电梯间调度,而不关心电梯内部调度的策略。

第三次作业增加了“订阅电梯”的选项。对于此次任务需要对乘客请求进行拆分,而乘客请求的“激活状态”需要实时更新。此次模型应用观察者模式(Observer Pattern)。调度器的实现采用观察者模式,订阅者即多部电梯,通过调度器的调度分配将新的请求加入到电梯对应的请求队列之中。调度器既负责输入请求的调度分配又能实时地订阅新的电梯。

总体来说架构比较完善,存在最大问题是从后两次作业电梯间优化策略写得比较杂乱。没有实现DIP原则,即抽象不能依赖于细节。因为电梯的实时运行状态需要依赖于调度器,而调度器又依赖于电梯。

img


2.3 第三单元

第三单元的内容依据社交系统依次展开;强调对JML语言和单元测试的理解。

总体来说,第三单元的架构较为朴素,思路较为简单,特别是前两次作业。第三次作业中才体现了OO对于封装类的理解。在我的架构中,新增了两个有关图的类,其中Point类和Edge类都比较简单,是辅助存储图的数据结构(点和边)。另外一个类MyTarjan,这是一个实现Tarjan算法的类。由于Tarjan算法较为复杂,笔者有意将其封装成类使用。但是这样的架构并不完善,因为其实有关图类的数据结构仍然耦合在社交系统内。思考后,认为更好的解决方式是抽象出一个只有点/边的MyGraph类。由于group属性不影响多数图相关的查询方法,整个network实际上是一张图。

此外,JML规格可以提高代码的正确性。它提供了规范代码的方法,通过形式化语言来撰写代码的规格,一方面利于前期的架构设计,更一方面也更容易测试分析。这一单元也对代码进行Junit单元测试(对某个方法进行测试),JMLUnitNG(自动生成边界数据),openJML(进行语法检查、静态检查和运行时检查)等等进行测试。


2.4 第四单元

第四单元的内容依据UML图解析依次展开;强调对UML图的理解。

总体来说,本单元的架构设计较为简单,只是略有不足,具体思考和改进在1.3中。UML图有利于对代码进行可视化建模,在2.2中的电梯调度中有所展示。此单元中,我们分析了三种图:类图是用来描述系统中类的静态结构;状态图则是描述状态到状态控制流,常用于动态特性建模;顺序图则表示对象之间的动态合作关系,强调对象发送消息的顺序,同时显示对象之间的交互。



3. 四个单元中测试理解与实践的演进


总结下来,在这四个单元的学习中,我们所掌握的测试方法不断增多,也能够不断深入了解体会如何针对性地对程序进行测试。没有一种测试方法是完全优于另一种的,只是在不同情况下有着不同的适用场景;通常多种测试方法结合。在四个单元中,笔者自己采用的、出现最多的有这几个方法:


1. 手动构造边界样例(适用于第一/四单元)

相信大家刚开始都会选择自己构造样例,然后利用python与同学的作业进行比对,从而检测准确性;但是这种检测方法通常只能覆盖一小部分。这种测试方法相对来说比较原始,自动化程度非常低,造成测试效率也很低;并且效果一般来说并不理想。譬如,第一个单元的作业特点是数据随机,边界数据的可能性较多;同时,第二三次作业也考察了程序的鲁棒性。在这种情况下,手动构造边界样例可能无法覆盖所有边界情况。

但同时,这也不代表这种方法没有它的价值。在第一单元的嵌套多项式求导中,笔者就出现了因为完全依赖自己写的对拍器而漏掉了某些特殊情况的问题,比如多个三角函数的嵌套$sin(sin(sin(sin(sin(sin(sin(sin(cos(x)))))))))$,在笔者写的数据生成器逻辑中(设定概率选择是否嵌套,多层嵌套的概率相当小),这样的数据几乎是很少很少的。

此外,第四单元也比较适用于这种方法,针对某一方法构造特定的UML图,从而精确检测代码的正确性。尤其是第一次作业的接口的多继承、类的对端问题,第三次作业的循环继承和重复继承的检查规则。比如构造多个循环继承的接口的UML图来检测Rule002。


2.编写自动化测试程序(适用于第一/二/三单元)

对拍器是个神奇的东西。它的逻辑也很简单,就是针对多个程序,自动生成数据进行比对正确性。其设计思路很简单,即

  • 生成测试数据。
  • 将测试数据分别输入到标称与自己的代码,并获得输出。
  • 对比两个输出是否相同。

其实对拍器只负责傻瓜地比对程序正确性而已,是否能够高效、准确地进行测试主要还是依赖于编写的数据生成器。在所有单元中,最适用于自动化测试的单元还是第二单元。第二单元的数据有几个特点,第一是形式比较复杂,第二是数据较多,第三是具有很大的随机性。这时只依赖手工测试可能强度就不够了,于是采用随机生成数据的方式。此外,第二单元的输出也有较大的差异性,需要针对性地构造条件来判断程序的正确性。

3.单元测试(适用于第三单元)

单元测试主要是用于验证服务中类方法或函数的行为。第三单元的主题就是JML语言。在这个单元中,出现了许多基于JML语言的测试,比如Junit单元测试(对某个方法进行测试),JMLUnitNG(自动生成边界数据),openJML(进行语法检查、静态检查和运行时检查)。当然,第三单元的输出较为固定,我依然采用了对拍器进行对拍,但是单元测试的好处就在于它能回归到代码逻辑,能够更精确地对于某个方法进行测试,从而更好定位程序的错误;这是与OO思想一脉秉承的。


当然,对于程序的测试远远不止这三种方法,比如还可以对代码进行形式化验证。相信以后学习的道路依然很长。



4. 课程收获


首先,笔者最大的感慨:面对对象不仅是一种方法,还是一种思想。课程全篇以JAVA语言为例,围绕着如何设计、如何优化展开。个人感受,数据结构和算法课告诉学生“怎么去解决某个问题”,而面向对象编程更多地强调的是”在实际应用中如何更好地解决这个问题“,这是与实际工程搭边儿的。给笔者印象较为深刻是编译课程的课设,它是实现一个小的工程,一个(优化比较鸡肋)的类C语言文法的编译器;课程虽然使用C++实现,但面对对象编程的内核是相同的,比如词法解析、词法解析等等,这些都是OO的思想,封装成类、单一原则。

面向对象这门课程最突出的优势,或者说是笔者最大的收获就是,让学生真正地去实践所学到的理论,而非纸上谈兵。本科三年的学习中,印象和理解最深入的可能是各种认真实践的课程设计。再好的理论也终觉浅,真正实践到代码,甚至仅仅是配置环境,都有着无数多以前从未思考过的问题。真正落实到工程上,才能更好地理解一些理论知识,比如”字符串的==和equals有什么区别“,”arraylist和hashset的contains又有什么区别”……

其次就是对于测试的不断理解。其实在这个学期里,按照课程指导书编写每次作业的时间花费并不多,相反地,更多的时间花在了写测试程序上。一般来讲,测试程序写的好的单元,公测互测错误也几乎没有;反而测试少的单元比如第四单元,可能出现的错误也比较多。

另外,这门课程不仅带给我知识上的收获,还有就是有关“人生的知识“。故事有点长,在此笔者不以赘述。总之,人生没有过不去的坎。



5. 课程改进建议


首先感谢为这门课程,和为之付出很多心血的老师们、助教们和同学们,也衷心祝愿这门课程越来越好,能够为来年的学生提供更好的训练。


5.1 课程内容

第二单元的作业部分建议适当调整。一是电梯间优化部分的分数可以减少/修改,个人而言,感觉电梯间的优化对不同数据有着差距很大的表现,特别是第二次作业。部分同学为了20分的性能分追求性能而忽略功能设计,我觉得是有悖课程初衷的。此外,第二/三次作业内容区别不大,仅仅增加了电梯的订阅。个人而言,第二次作业就实现了订阅者模式,课程组可以考虑增加后续考察强度。


5.2 课上实验

这学期的课上实验还是比较紧凑的,中间也陆续出过不少问题。理解老师和助教都比较辛苦,但是对于同学而言,课上实验缺少及时反馈。更改的代码对不对?填写的答案合理与否?课上实验是与课下实践相辅相成的,如果能够实现自动化测试并且课后及时对课上实验的结果进行反馈,相信对于学生对某部分知识的理解有所帮助,从而利于接下来的课下作业的设计。


5.3 WeWork平台的使用

个人感觉,WeWork是一个很好的消息提示平台,希望可以进一步好好利用。比如能够实现跟OO课程平台的同步,从而使得任何通知可以及时在WeWork上同步通知,造福一下不经常上线OO平台而错过指导书更新的星际玩家。



6. 线上课程体会


多亏较为成熟的课程平台和作业评价机制,OO是一门非常适合线上教学的课程。

其实现在网络授课早已是大势所趋,不过恰巧在2020疫情期间,我们“有幸”实践了一把。尤其是对于OO这门课程,其实网上教学更节省(课堂来回路程的)时间。讲课也并非单方面授课,讨论中心可以很及时地问清楚。个人而言,体感OO线上线下差距不大。当然,考虑到有些同学家庭条件等等的限制,线上教学仍然是一种后备之选。

最后,在这样一个前所未有的“多事之秋”,不妨引用《双城记》中的一段话:

这是希望之春,这是绝望之冬;

人们正直登天堂,人们正直坠地狱;

我们一无所有,我们巍然矗立。

原文地址:https://www.cnblogs.com/daytripper/p/13166499.html