【BUAAOO】第四次博客作业

【BUAAOO】第四次博客作业

说点闲话

繁忙了一个学期的面向对象课程终于快要正式结束了,虽然忙,却苦乐参半。许多“第一次”献给了OO——第一次熬夜,第一次高强度代码训练,第一次独立完成像样的代码,第一次认真为bug抓心挠肝,第一次为已知的、却不愿改正的错误付出惨痛的代价。
说真心话,本学期的课程让我真切地感受到自己与“聪明人”之间无法逾越的鸿沟。上学期的计组虽然成绩也不是很好,但总结下来,体现出智商和知识广度差距的地方并不多。但是本学期的操作系统和面向对象两门课,着实让我认清了自己是多么弱小。
我一直认为自己不够聪明,几乎所有的成绩都是努力换来的,面对问题,所有的解决方法都是最笨的、最基础的。我感叹其他同学对问题的精准把握,也为他们解决问题利落高效的方法感到惊奇,但我始终无法成为“那群人”中的一员。理解力、执行力、甚至精力,相比其他同学,我总是差那么一截。“可能是我太懒了”,“我真的想不出来”,“题目太难了,我不能理解”……各式各样的理由把自己包裹起来,不愿意面对现实中难以解决的问题。
是我懦弱了。
总是仰慕着别人,然后感叹自己的无力,我也不知道怎么走出这个怪圈。我总指望着谁能提供一种可以遵循的方法,那么我就沿着这条路走吧。可能这就是我和强者的差距吧。强者引领着时代,而弱者只能等待消亡。
说了这么多丧气的话,其实也没有很低沉啦~
这学期的课虽然忙,却着实让我学到了不少东西。尤其是面向对象的思维,不仅可以应用于编程,生活中许许多多的问题都可以用这种分层分块的思想解决。
本学期确实是锻炼心性的四个月。没有周末、没有休息、硬核课程扎堆的每一天,很多同学都叫苦不迭。我也知道很痛苦,熬夜伤身,越来越臃肿的身形压迫着心脏,仿佛每熬一次夜,第二天就要回天家了。但是,我觉得谁能坚持得住,谁就赢了。可能赢的不是成绩,但是品尝了痛苦、挑战、打击后还能站起来并且走下去的,用一句俗套的话来讲,就是“精神强者”。虽然这个世代,世俗不需要没有能力、没有成绩的“强者”,但是不快乐的“强者”与工具有什么区别?可能“强者”的快乐就在于创新与挑战吧,这我无从知晓。
我身边有许多很强的朋友,我很敬佩他们的努力与智力。但我发现了我认为很让人难过的现象——我的朋友们心态都很不好。较真是好事,但是因为与别人一点点的差距便怨声载道,因为一次小小的失误便整天阴郁,满脸写着“生而为人,我很抱歉”。我不知道是我太弱了还是怎么回事,我无法体会这种心态,也不愿意体会。
朋友们,你们首先要成为“人”啊!心中充满喜乐与希望,每一天有指望的活着,才能去追求其他的美好啊!别总用“菜”来自嘲,别总和别人比较,活着要有更深远的意义啊!之前我也常常把“我太菜了”挂在嘴边,现在,当我意识到这种自嘲带来的极大负能量之后,我决定不再说了,尽管我依然能力平平,但我为着自己的乐观骄傲。
“生而为人,骄傲一些!”朋友们加油!

正文开始

第四单元UML图解析作业总结

一、第一次作业架构设计

初次接触UML的时候,我的想法和老师说的反例一样——“这个东西不就是用来画图的吗???”直到老师将UML的厉害用法向我们介绍之后,我才对这种神奇建模语言刮目相看。老师的展示上,类间关系、执行顺序关系、状态转移关系一目了然,不禁让人感叹——自己之前写的代码要是先列出类图岂不是美滋滋?
下面来看看架构设计吧。

  1. 类设计
    由于设计问题,本次作业除了主类外,我只使用了一个MyUMLInteraction类用以实现各种查询及存储功能。最开始时,我有想法对每一种查询指令涉及的对象建立单独类,但是碍于设计上的缺陷,MyUMLInteraction类与其他类出现了互相调用数据结构的尴尬状况,为了避免麻烦(懒),我还是将所有的功能实现结合到了一个类中。(就这样,写了一坨*)
  2. 功能设计
    为了实现各种查询功能,我使用了如下数据结构。

     private HashMap<String, UmlElement> eleList = new HashMap<>();
     private HashMap<String, UmlClass> classList = new HashMap<>();
     private HashMap<String, UmlAttribute> attrList = new HashMap<>();
     private HashMap<String, UmlOperation> opList = new HashMap<>();
     private HashMap<String, UmlParameter> paraList = new HashMap<>();
     private HashMap<String, UmlGeneralization> geneList = new HashMap<>();
     private HashMap<String, UmlInterface> interfaceList = new HashMap<>();
     private HashMap<String, UmlInterfaceRealization>
             intRealList = new HashMap<>();
     private HashMap<String, UmlAssociation> assoList = new HashMap<>();
     private HashMap<String, UmlAssociationEnd> assoEndList = new HashMap<>();

    eleList:存储类型为UmlElement的元素
    classList:存储类型为UmlClass的元素
    attrList:存储类型为UmlAttribute的元素
    opList:存储类型为UmlOperation的元素
    paraList:存储类型为UmlParameter的元素
    geneList:存储类型为UmlGeneralization的元素
    interfaceList:存储类型为UmlInterface的元素
    intRealList:存储类型为UmlInterfaceRealizetion的元素
    assoList:存储类型为UmlAssociation的元素
    assoEndList存储类型为UmlAssociationEnd的元素

    以上的数据结构用于存储官方包解析的各种类型元素,规格皆为“ID-元素”的形式。由于类图中可能存在重名的问题,ID便是区分每一个同类型同名元素的直接途径。但是由于同名类和属性违反了UML的建模规则,本次类图解析当作异常处理,因而不会对重名的类与属性进行相关查询,所以未将这些元素从Map中去除变得不置可否。

    下面便是针对每一种查询建立的数据结构。

     private HashMap<String, Integer> clsNmSta = new HashMap<>();
     private HashMap<String, ArrayList<UmlClass>> classFathers = new HashMap<>();
     private HashMap<String, ArrayList<UmlInterface>> interfaceFathers = new HashMap<>();
     private HashMap<String, HashSet<String>> intFather = new HashMap<>();
     private HashMap<String, String> clsNameToId = new HashMap<>();
     private HashMap<String, List<String>> clsNameToInf = new HashMap<>();
     private HashMap<String, Integer> clsIdToAsso = new HashMap<>();
     private HashMap<String, HashSet<UmlClass>> clsIdToAssoCls = new HashMap<>();
     private HashMap<String, String> topClass = new HashMap<>();
     private HashMap<String, HashMap<OperationQueryType, Integer>> clsToOpMode = new HashMap<>();
     private HashMap<String, ArrayList<UmlParameter>> opAdPr = new HashMap<>();
     private HashMap<String, ArrayList<UmlAttribute>> clsAndAttr = new HashMap<>();
     private HashMap<String, ArrayList<UmlOperation>> clsAndOp = new HashMap<>();

    clsNmSta:用于记录每一个属性为UmlClass的元素在元素列表中出现的次数,以此来判别该类是否是重名类。规格为“类名-次数”
    classFathers:通过getClsFathers方法,使用类似bfs的方法,找到所有类为起点子类的所有继承的各级父类。从而找到顶级父类。规格为“ID-父类列表”
    interfaceFathers:通过getInfFathers方法,找到每个接口继承的直接父类,类似于邻接矩阵。规格为“ID-直接父接口列表”
    intFather:在查询时进行缓存的接口的各级父接口。使用dfs的getFather方法,没有进行预处理,而是在每次查询的时候再返回查询结果的同时将结果送入缓存。规格为“ID-父接口ID列表”
    clsNameToId:用于存储类名对应的id。由于同名类会抛出异常,所以出现同名的类不会进入到后续查询,在数据结构中存在基本无用。规格为“Name-ID”
    clsNameToInf:存储类名到所有应用的接口列表的映射。此处应用了递归查询到的intFather结果,返回结果为目标类本身和继承父类所有应用的接口。规格为“类名-接口名列表”
    clsIdToAsso:存储类ID到所有关联的数量。规格为“类ID-Num”
    clsIdToAssoCls:存储目标类到所有关联类的映射。由于关联只关系到直接关系,所以依据AssociationAssociationEnd进行直接链接便可得到。规格为“类ID-关联类列表”
    topClass:顶级父类列表。规格为“根类名-顶级父类ID”
    clsToOpMode:用于存储查询得到的类名对应的方法参数类型结果,规格为“类ID-查询类型-数量”
    opAdPr:存储方法所包含的所有参数。规格为“方法ID-参数列表”
    clsAndAttr:类到所拥有属性的列表。规格为“类ID-属性列表”
    clsAndOp:类到所使用方法(包括继承)的列表。规格为“类ID-Op列表”

    我所使用的策略是能提前缓存时就缓存,不然就每次查询时进行缓存。

    最重要的、也是本次作业的核心结构在于如何通过关联关系和继承关系建立起各级类、各级接口的关系网。对于类间关系,我选择在构造MyUmlInteraction类时就将网络搭建好。

    但是这样就出现了一个问题,也就是这个问题导致我第二次作业出现了极难弥补的设计错误。第一次作业不涉及图的规范性检查,所以输入的数据都是合法的,即类和接口没有循环继承。这样每一个类都会查到相关的顶级父类,dfs也会自动退出。但是当出现环时,由于在构造时就进行了类图的构建,查询会出现无法退出的死循环情况,极为致命。第二次作业架构分析再进行详细的剖析。

    在本次作业中,还有需要注意的问题就是查询数据结构时的空指针问题。许多数据结构是逐层嵌套的,每次尝试取数据时都需要判断键的存在性和值的存在性,否则就会出错。

二、第二次作业架构设计

  1. 类设计
    由于查询功能的增加,代码行数有限制,无法将所有功能都集中在一个核心类中,所以本次作业将四个类型的查询功能分成四个小类,之后再集成到核心类中。
    • MyUmlClassInteraction:负责处理有关类的查询指令。这个类与前一次作业中的主类基本无异,在此不作赘述。唯一的变化是将一些需要的数据结构通过参数形式传入,在实例化时得到结果。
    • MyUmlCollaborationInteraction:负责处理有关顺序图中各种信息查询的类。查询方式与Class的查询方式基本类似,处理方式也类似,所以都有对参与元素数量统计的数据结构,以便判断是否存在同名重复元素。
    • MyUmlStateChartInteraction:负责处理有关状态图中各种信息查询的类。状态图的处理相对麻烦一些,但也类似于类查询相关父类的操作。使用DFS算法可以对State通过Transition形成了有向图进行遍历,从而查找出每一个State为起点所能到达的所有后继状态。需要注意的是对PseudoState和FinalState的特殊处理。
    • MyUmlStandardPreCheck:对UML规格的基本检查类。经过实际使用,检查是在构造方法后启动,针对002规则(类属性重名规则)、008规则(类和接口的循环继承规则)、009规则(类和接口的重复继承规则)进行检查。这部分主要应用的算法仍然是DFS算法,分别对类的有向邻接矩阵、接口的有向邻接矩阵、类和接口混合的有向邻接矩阵进行遍历,从而查询是否有环出现。
    • MyGeneralInteraction:顶层类,用于输入数据以及对应输出查询指令接口。此处可以进行一些对一定正确的数据集合(检查中不会出错)的整理。
  2. 功能设计
    • MyUmlClassInteraction:本类主要使用的数据结构与前一次基本没有区别。
    • MyUmlCollaborationInteraction:本类主要使用的数据结构如下:
      private HashMap<String, UmlInteraction> intactList; private HashMap<String, UmlLifeline> lifLnList; private HashMap<String, UmlMessage> msgList; // name private HashMap<String, Integer> intactNmSta; private HashMap<String, String> intactNameToId; private HashMap<String, Integer> lfLnNmSta; private HashMap<String, String> lfLnNameToId; // id private HashMap<String, ArrayList<UmlLifeline>> intactToLfLn = new HashMap<>(); private HashMap<String, ArrayList<UmlMessage>> intactToMsg = new HashMap<>();

      intactList:记录每一个interaction的数据结构,规格为“ID-interaction”
      lifLnList:记录每一个lifeline的数据结构,规格为“ID-lifeline”
      msgList:记录每一个message的数据结构,规格为“ID-message”
      intactNmSta:统计每一个interaction对应名字的数量,从而判断该元素是否存在重名错误。规格为“Name-出现次数”
      intactNameToId:通过名字查询对应ID的数据结构,虽然同名元素会被后来元素覆盖,但是由于同名行为属于异常,所以覆盖并无影响。规格为“Name-ID”
      lfLnNmSta:lifeline的名字数量统计,从而判断lifeline是否存在重名行为。规格为“Name-出现次数”
      lfLnNameToId:lifeline名字对应Id的数据结构,与intactNameToId行为相同,规格为“Name-ID”
      intactToLfLn:interaction对应lifeline的列表。规格为“ID-lifelineList”
      intactToMsg:interaction对应message的列表。规格为“ID-messageList”

      本类中不涉及很复杂的算法,查询功能为getParticipantCountgetMessageCountgetIncomingMessageCount。查询前,首先需要处理异常行为,可以通过对intactNmSta的查询判断interaction是否存在和是否出现重名行为。

      正式查询功能:
      • getParticipantCount:可以通过intactToLfLn的缓存查询。如果缓存中没有,那么就对lifeline列表遍历,寻找parentId与对应interaction的ID相同的lifeline,计入count。
      • getMessageCount:与lifeline的查询方式相同,同样是先对缓存查询,之后再判断是否需要遍历。
      • getIncomingMessageCount:需要进行两步判断。首先是interaction的基本判断,与前两个查询方法没有差别;其次是对lifeline的判断,这部分需要对得到的intactToLfLn进行查询,从而判断lifeline的存在与否与重名与否。之后再对intactToMsg查询,找出incoming的message。
    • MyUmlStateChartInteraction:本类针对状态图进行查询,主要使用如下的数据结构:
      private HashMap<String, UmlState> stateList; private HashMap<String, UmlTransition> tranList; private HashMap<String, UmlFinalState> fnlStatList; private HashMap<String, UmlStateMachine> statMacList; private HashMap<String, UmlPseudostate> psDoStatList; private HashMap<String, UmlRegion> rgnList; private HashMap<String, Integer> stmNmSta; private HashMap<String, String> stmNameToId; private HashMap<String, Integer> stNmSta; private HashMap<String, String> stNameToId; private HashMap<String, ArrayList<UmlState>> stmToSt = new HashMap<>(); private HashMap<String, ArrayList<UmlTransition>> stmToTr = new HashMap<>(); private HashMap<String, HashMap<String, Boolean>> stateAjMtx; private HashMap<String, HashSet<String>> stateRchMtx = new HashMap<>();

      前十个数据结构与其他类中同类型数据结构基本类似,此处不作多余解释。
      stmToSt:状态机对应状态的列表,通过StateMachine的Id可以查询到状态机里拥有多少状态。不包含初始状态和结束状态。规格为“ID-状态列表”
      stmToTr:状态机对应transition的列表,包含所有的状态转移,包括从初始状态出发和归于结束状态的。规格为“ID-转移列表”
      stateAjMtx:状态邻接矩阵,通过transition建立,在实例化时传入。规格为“ID-ID-是否邻接flag”
      stateRchMtx:状态可达矩阵。规格为“ID-可达ID列表”

      本类涉及到的查询方法中,只有查询后继状态相对麻烦一些。使用深度优先搜索算法对后继状态遍历从而建立可达矩阵。需要注意的是,可达矩阵的建立不可以在核心类构造方法中实现,一旦出现循环转换,容易出现无法跳出的死循环,应当抛出异常,却卡死在构造中,无法进行precheck。其他两个查询相对简单。

    • MyUmlStandardPreCheck:本次作业中公认最难写的类。相对复杂的结构体现在使用了三种不同的深度优先搜索算法。
      • checkForUml002():对002规则,即类属性重名进行检查。对类内部的属性进行重名排查,同时对关联对端的AssociationEnd进行重名排查。
      • checkForUml008():对008规则,即类与接口的循环继承进行检查。由于类与接口之间的实现不存在循环实现一说,所以分别对class和interface列表进行深搜,找到所有级次的父类和父接口,从而判断是否出现002错误。
      • checkForUml009():对009规则,即类与接口的重复继承进行检查。对于本检查,我们应当建立类和接口的混合邻接矩阵,从而判断可达性。

        需要特别注意的是,正确性的检查是在核心类的构造方法之后进行的,所以对于可能存在错误的数据群,断不可在构造方法中进行数据结构的生成。本次作业我在这个地方吃了极大的亏,沿用了上次作业中对父类的深搜处理,没有对循环继承进行判断,导致死循环的出现,是架构上非常恶心的错误。这次作业给我一个教训——能不在构造方法中初始化缓存就不要这么做,除非对输入的数据有100%正确度的确信——当然这是不可能的。先判断数据正确性再对数据操作是至关重要的。

    • MyGeneralInteraction:顶层类,主要是对数据归类和调用其他类实例的方法,从而实现查询功能。本类中使用了一些预处理,但是通过本次实验惨痛的教训,下次实验再写构造方法时,我不敢这么做了……

四个单元作业的架构演进以及对OO的理解

经过一个学期的面向对象课程学习,在学期末回头看当初写过的代码、练过的题目,仍然不禁感叹,DDL真的是第一生产力(假的)!从第一单元的字符串求导化简处理,到第二单元的电梯,再到JML规格化描述语言,直到最后的UML建模语言,OO的思路是从抽象到具体再到抽象的过程。前两个单元里面,我们熟悉了对象的基本构建方法,了解了对象之间如何协同工作,在单线程与多线程之间安全切换。如果有时间,真的恨不得把能细分的元素都变成对象。第三单元的规格化建模训练中,可以看得出规定输出结果后的正确性保证。没有对算法的限制,在保证输出结果的正确时,可以极大提高性能。第四单元UML建模语言的训练让我更好的理解了“万物皆对象”的思想。一个类、一种方法、甚至一个节点都可以成为一个对象,定义——实例化——存储——利用,对事物的划分越清楚,问题的结构就越清晰,再复杂的问题都可以有破解的入口。这就是OO的哲学吧,科学中的哲学,也是生活中的哲学。

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

测试还是先找自己的代码可能出现的问题,把这些问题拿去和同学们交流一下,再在互测平台试一试,可能就撞上了哈哈哈。其实多数情况下还是白嫖大佬们的对拍器的,感谢LPX巨佬每次孜孜不倦研制出的对拍器(写的对拍器的代码量可能都不亚于OO作业的代码量了,可怕)

说实话,一个寒假没怎么碰过代码,都不怎么会敲码了,上这一门课时,我是惊慌失措的。连A+B都不会写,语法更是不了解,真的很慌。好在有同学们的帮助,老师们的认真讲解,我才能像现在一样独立写出较小规模的“工程”(虽然说都是一坨坨shi)。

回想这一学期,确实是印证了我在前面博客中引用的话——“你必忘记你的苦楚,就是想起也如流过去的水一样”。感谢走过的路、遇见的人和一直陪伴我的各位,这一路上,苦楚有,快乐也有,但能留下来的,必定不是苦楚,只有快乐和感激。谢谢!

改进建议

说实话,面向对象课程体验真的很好,从课程设置、教学内容到测试平台,每一次课程和作业都有令人惊奇的地方。虽然也不是完美的,但是仍然是值得参加并且一定会有收获的课程。有以下几点建议:

  • 课程难度梯度设置需要有一些调整。在前两单元的作业中,都出现了第二次作业和第三次作业难度梯度陡增的情况。希望助教和老师可以调整得更合理一些。

  • 引进更加普遍适用的编程方法和建模方法,尽量与企业能有对接之处。

  • 训练强度可以稍微降低一些,毕竟其他课程也很吃时间的……

总结

都写在前面啦,不啰嗦啦!

谢谢你能看到这里啦!

猜你喜欢

转载自www.cnblogs.com/dzlbuaa/p/11073435.html