jixuchongai

导航

 

OO第四单元总结

本单元架构设计

本单元作业核心是为了实现UML解析器,对UML类图、顺序图和状态图进行解析,并进行一定的有效性检查,针对不同的功能查询我分别采用了不同的并列结构来实现。

UML类图

UML类图我采用了两套树结构,第一棵树是UML类图中各种元素的关系树:

 

 

第二棵树是UML中类与接口的继承关系树,目的是为了支持继承深度以及实现接口。方法与参数的继承

具体采用JAVA内置的HashMap储存,以元素的id作为键值,方便查询。

在根据输入的UML元素建树时,采用按照第一棵树的层次深度遍历的方式,第一次遍历输入UMLClass与UMLInterface这两个顶层元素,第二次建树输入UMLOperation、UMLAttribute与接口继承关系,第三次建树输入UMLParameter以及接口的实现,最后一次输入类的继承关系。同时在建树的过程中动态的维护各种信息,以便直接查询。

而对于第二棵树,需要考虑的有子类数量、继承深度、属性耦合度与实现的接口。为了在类继承关系时可以方便的实现动态增加与维护,我把四者的输入放在了前三轮循环中,在第四轮循环才输入类的继承关系。

为了保证性能,我把静态数组变为了动态数组,每一轮循环都使用迭代器进行遍历和remove

UML顺序图

UML顺序图的查询基本都不需要复杂的算法,且其本身也不存在树或者图结构,因此只需要建立顺序图元素数据结构关系树这一颗树即可。遍历的方法和逻辑与UML类图基本相同,都是按照层次深度进行遍历与输入,先输入父元素再输入子元素,并动态维护。

值得注意的是,面对可能抛出多种异常的查询指令,其异常本身也存在着不同的层次,因此对于异常我也采用了分层的思想,是哪一层的异常就在哪一层抛出。例如:

//MyImplementation
@Override
   public UmlLifeline getParticipantCreator(String interactionName, String lifelineName) throws
           InteractionNotFoundException, InteractionDuplicatedException,
           LifelineNotFoundException, LifelineDuplicatedException,
           LifelineNeverCreatedException, LifelineCreatedRepeatedlyException {
       if (!nameInteraction.containsKey(interactionName)) {
           throw new InteractionNotFoundException(interactionName);
      } else if (interactionCount.get(interactionName) > 1) {
           throw new InteractionDuplicatedException(interactionName);
      } else {
           return nameInteraction.get(interactionName).getCreate(lifelineName);
      }
  }
//顶层只检测了前两种异常,把剩余四种异常的处理留在下一层处理:
//MyInteraction
public UmlLifeline getCreate(String lifelineName) throws LifelineNotFoundException,
           LifelineDuplicatedException, LifelineNeverCreatedException,
           LifelineCreatedRepeatedlyException {
       if (!nameLifeline.containsKey(lifelineName)) {
           throw new LifelineNotFoundException(name, lifelineName);
      } else if (lifelineCount.get(lifelineName) > 1) {
           throw new LifelineDuplicatedException(name, lifelineName);
      } else {
           return nameLifeline.get(lifelineName).getCreate(name);
      }
  }
//MyLifeline中的叶子函数:
public UmlLifeline getCreate(String interactionName) throws LifelineNeverCreatedException,
           LifelineCreatedRepeatedlyException {
       if (createCount == 0) {
           throw new LifelineNeverCreatedException(interactionName, name);
      } else if (createCount > 1) {
           throw new LifelineCreatedRepeatedlyException(interactionName, name);
      } else {
           return create;
      }
  }

这样处理使得结构更加清晰,把异常处理也分散到每一层进行处理,有效降低了复杂度

UML状态图

UML状态图采用了与UML类图类似的处理方式,再不同的循环中建立元素树并动态维护;并采用了与UML状态图类似的异常分层处理的方法。

状态图自己的特点在于,顾名思义,需要建立一个图结构,储存状态之间的迁入与迁出关系。对于查询触发事件,只需要分层处理异常并输出最终动态维护的事件列表即可;对于关键状态查询,我采取的办法是,在去掉查询的状态后,通过dfs判断是否可以由起始状态到最终状态,若不可以且起初可以,则为关键状态。

UML有效性检查

为了满足代码风格的约束,我单独设置了Mycheck类进行有效性检查,并在正式建树前先进行有效性检查建树,如果发现了非法情况,就直接退出建树,以提高性能并防止死循环等问题的出现。

有效性检查采用与正式建树基本相同的思路,需要额外注意的是循环继承和重复继承的检查。对于重复继承,我采用数组记录继承的所有父亲。如果在加入时发现重复,则意味着重复继承。此外,如果父亲已经重复继承,则子类/接口必然重复继承,这也是需要关注的。

对于循环继承,由于类是单继承,故仅需要考虑接口即可。接口存在着多继承,故接口可能有多个父接口和多个子接口,因此相对较为复杂。我原本采用了UML类图中递归的方法,但在面对一个接口处于多个继承环中的情况时出现了bug,原因是如果该接口已经处于一个继承环中时会停止检索,导致有些接口的循环继承无法被统计。因此我在每个接口内部储存其全部因继承关系产生的“祖先”与“后代”,改用双重循环添加关系的方法来实现继承。

四个单元中架构设计思维及OO方法理解的演进

第一单元

第一单元主要是层次化设计,主要涉及:

  • 递归下降的思想,即表达式解析的递归实现。采用expr—term—factor三级结构,使用lexer词法词法转换器分析文法以及parser解析器来解析表达式并储存。

  • 以及分层实现表达式解析,即表达式运算的递归实现。面对众多的情况,最好的方法就是进行统一处理,将所有项都转化为“系数-指数-sin组-cos组”的形式,以进行统一的运算和合并。这样我们可以忽略各种差异,只关注统一的运算法则以及怎样把各种项都转化为统一格式即可。

  • 自定义函数的处理:同样采用了递归代换和处理的思想来把形参替换为实参。

  • 输出的处理:鉴于三角因子与表达式的相互包含与调用,输出也需要递归的方法,先输出“sin( / cos(”,再调用多项式的输出方法。

总而言之,第一单元的核心就是层次化设计,在面对复杂的问题需求时,把问题分解为不同的层次,自上而下分解问题以及需求,自下而上解决问题,这就是层次化设计的好处,递归下降只是其中的一个例子。

此外,我初步理解了“封装”的思想以及工厂模式,把关联紧密的数据与方法结合为一个类,在内部实现,对外隐藏细节仅保留交互接口,以实现“高内聚,低耦合”的状态

第二单元

第二单元主要涉及多线程设计。如果说第一单元作业仅仅是面对一个静态的输入,使用层次化设计即可,那么第二单元由于实时性的需求,还需加入支持实时交互的架构,因此引入

  • 多线程,不同的线程构成了问题处理(即乘客需求响应及解决)的不同层次,通过共享对象来实现交互。为了使问题解决的层次更加清晰,

  • 同步块与锁、多线程编程为了加快程序运行的速度,它们共享资源自然会带来类似于流水线CPU中读写不一致的问题,因此需要加锁进行保护。加锁地方有两处,一是共享对象本身,二是线程内部对共享对象进行操作时。

此外,我还了解了生产者-消费者模式、单例模式、流水线模式和单一职责原则等设计模式和原则

第三单元

第三单元主要是规格化设计涉及到:

  • 契约式编程,需求者按照约定的规格来表达需求,而编程者按照规格来实现需求,这样可以避免自然语言的诸多缺陷。此外,在阅读规格了解了核心需求后,不应拘泥于规格的描述,而是应在满足需求的基础上采取高效的算法来实现以保证性能。这也正是第三单元采用了并查集、最小生成树、最短路径以及动态维护等诸多算法的原因之一。

  • 程序的鲁棒性。第三单元的方法中都有加入抛出异常,这样可以增强程序健壮性,应对各种非法的数据。这也为第四单元进一步异常的层次化处理打下了基础。

此外,这一单元我还了解了代理模式、外观模式等设计模式,各种功能在顶层进行,具体实现交由下层,这也是层次化设计思想的延申,为第四单元UML解析器的设计奠定了很好的基础。

第四单元

第四单元中,我学习到了UML语言中的类图、时序图与状态图,并在理解UML中各种元素之间的关系的基础上,建立起树、图等数据结构对这些元素进行储存、管理和查询,并支持了一定的合法性检查。

具体的架构心得前文已经提到,这里需要额外提到的一点是,本单元引导我学会了,在面对诸多的输入信息时以需求为导向,过滤掉无用信息而只关心核心需求,这样可以使思路更加清晰。

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

第一单元

第一单元中,由于sympy库可以验证运算结果的正确性,因此可以方便地实现对拍。生成随机数据大量轰炸可以有效发现bug,但由于数据生成器本身的设计,难免会有覆盖不全的情况,因此需要采用手工构造来结合。

此外,sympy库仅可以检查结果正确性,但无法验证格式正确性,导致我出现了sin(-x)这种格式错误,这也启示我要多做一些白盒测试,阅读自身代码。

第二单元

在经历第一单元启示大量阅读了自己代码后,白痴型的错误有效减少。此外,针对CPU时间的问题,在正确性的基础上有加入了CPU时间等指标,并在Linux环境中运行以及比对,这样可以有效发现自己的性能缺陷并改正。

第三单元

对于自动化评测,除了数据生成器之外,自动执行脚本也是重要的一环。这方面我主要向张凯歌同学和强生同学学习,从最开始支持正确性比对,到支持时间查看,再到最终实现了更好的“用户界面”效果。

此外,还针对JML的特点采用了JUnit进行单元测试。

第四单元

与前面基本相同,由于最后一次作业限制较多难以自动化测试,故采用了手工数据覆盖和回归测试。

课程收获

  1. 从0开始学习了JAVA语言

  2. 初步接触学习了面向对象这一现代主流编程思想,以及诸多的架构和设计思维

  3. 学习了自动测试、手动构造、JUnit等诸多测试方法思想

  4. 加强了工程级别的代码实现能力,并优化了代码风格

  5. 阅读理解JML与UML代码

  6. (和OS一起)熟悉了git的使用、idea的使用技巧等工具使用能力,明白了工具对于高效的编程的重要性

课程的具体改进建议

第一单元

第一单元的作业比较复杂,但此时同学们面向对象思想还没有特别成熟,难免由于对架构与的重要性认识不足,而设计出低效的架构,有同学甚至开始时仍然采用墨面向过程的思想设计出几百行的单个类。对此,我认为应该让同学们认识到架构的重要性,引导他们多去看指导书和训练课程,并通过一定的限制(如限制单个类的行数与复杂度等),来让同学们意识到把复杂度分散的重要性,更好地设计出优秀的架构。

第四单元

OOpre中,pre3task5是针对UML的训练,其让同学们自行画出自己pre2task5的UML类图并解析。而我们这学期第一与第二单元的作业都是很好的学习范例,不仅存在一定的复杂度和梯度,而且是同学们自行设计出来的。因此我认为可以在第四单元设计解析自己前两单元的UML解析器,这样同学们印象更加深刻,以达到更好的效果

测试

对于测试而言,由于强测数据的黑盒性质,以及很多同学对于自动评测机的实现不是很了解,可以说测试的掌握程度远不如面向对象思想。实际上,我认为测试也属于软件能力培养的重要一环。因此我认为应该增加一定的对于自动测试的教授(如怎样设计数据生成器、怎样结合本地评测环境实现自动化脚本),让同学们不仅会完成程序,更会测试程序,完成软件能力的全方面培养",

结语

回顾一学期的OO课程,我与OO周旋的过程,正是我与我周旋的缩影。从最开始抱怨OOpre的难度,到学期中间顶住一天一个ddl的强度完成OO并拿到99分的强测,再到debug到周六晚七点才提交终版,直至今天完成了全部任务后,才发现已经在不知不觉中完成了将近一万行的代码,面向对象思想和编程能力也大有提升。如果没有这种高强度的push,就不会有能力的快速长进,这也正是本科上半场的真实缩影。

此外,坚持下来课程的过程也进一步坚定了我的信心。遥想大一程设三次一千名,数据结构期中更是爆零,让我一度怀疑自己是不是只会做题,对于编程就不甚了然。但这一年坚持下来COOOS课程、尤其是完成了OO小型工程级别的任务后,我不再怀疑自己,不再妄自菲薄,也可以说完全适应了在六系的生活。这种信心的培养和意志的磨砺,在某种意义上比编码能力本身的锻炼来说更重要。

当然,我本学期的OO课程还有诸多遗憾。第一单元未实现三角化简、第二单元未实现更高效的电梯调度算法,第三单元dijkstra没实现堆优化,第四单元查询没实现tarjan;此外,数据生成器与自动化脚本都还欠缺很多,在线实验还没有充分消化吸收,对于JML和UML的掌握还比较浅层,博客的搭建与风格还有待提高......希望随着年级都增长,这些问题可以解决,转化为自身的能力。

话已至此,我想起上学期CO课高老师课件中的一句话:“当课程结束后,电路、指令集、汇编、流水线等等,这一切的一切都会变得模糊。但我非常希望你们能掌握这样的思维方法,具备这样的能力。我坚信这些方法与能力未来会在硬件领域乃至技术领域之外,展现它的效力。“那么对于OO,就化用马尔克斯《百年孤独》的篇首句作为本学期OO的结语吧!“许多年以后,面对团队的工程项目,他将会回想起,OO课程带他去见识精妙架构的那个遥远的下午。”

posted on 2022-06-29 11:57  继续宠爱  阅读(16)  评论(0编辑  收藏  举报