OO终章

OO终章

本单元两次作业架构设计

本单元处理的是UML的解析任务,需要对类图,顺序图和状态图进行相应的查询和统计,并对一致性进行检验。

在两次作业中,我保持了同样的架构设计,并未进行重构处理,下面描述一下我的结构核心思想。

本质上,我们需要对相应接口的方法进行实现,因此将整体架构分为两部分:构造并填充相应数据结构,对不同指令设计相应算法利用数据结构进行处理。

对于数据结构的构建,事实上与需求相关,即需要完成的指令,不需要的即不用构造。

下面我对我所构建的数据结构进行总结(以第二次作业为准)

private HashMap<String, UmlClass> classidToClass = new HashMap<>();
    private HashMap<String, HashSet<UmlClass>> classNameToClass
            = new HashMap<>();
    private HashMap<String, HashMap<String, HashSet<UmlOperation>>>
            classIdToOperationName = new HashMap<>();
    private HashMap<String, HashSet<UmlOperation>>
            classidToOperationId = new HashMap<>();
    private HashMap<String, HashSet<UmlAttribute>>
            classIdToAttribute = new HashMap<>();
    private HashMap<String, HashMap<String, HashSet<UmlAttribute>>>
            classidtoattributenname = new HashMap<>();
    private HashMap<String, ArrayList<String>>
            generalizationSonToFather
            = new HashMap<>();

    public HashMap<String, UmlInterface> getInterfaceIdToInterface() {
        return interfaceIdToInterface;
    }

    private HashMap<String, UmlInterface>
            interfaceIdToInterface = new HashMap<>();
    private HashMap<String, HashSet<OperationQueryType>>
            operationidtotype = new HashMap<>();
    private HashMap<String, String>
            associationEndIdToAssociationEndId = new HashMap<>();
    private HashMap<String, HashSet<String>>
            idToAssociationItSelfSet = new HashMap<>();
    private HashMap<String, String>
            assiciationEndIdToassociationName = new HashMap<>();
    private HashMap<String, String>
            associationEndIdToitselfId = new HashMap<>();
    private HashMap<String, ArrayList<String>>
            relizationidtointerfaceid = new HashMap<>();
    private HashMap<String, HashMap<OperationQueryType, Integer>>
            resultClassNameOperationCount = new HashMap<>();
    private HashMap<String, HashMap<AttributeQueryType, Integer>>
            resultClassNameAttributeCount = new HashMap<>();
    private HashMap<String, Integer> resultClassNameAssociationCount
            = new HashMap<>();
    private HashMap<String, ArrayList<String>> resultClassNameAssociationName
            = new HashMap<>();
    private HashMap<String, ArrayList<AttributeClassInformation>>
            resultClassNameHide = new HashMap<>();
    private HashMap<String, String> interfaceIdToName = new HashMap<>();
    private HashSet<String> resultNameHideSetId = new HashSet<>();
    private HashMap<String, HashSet<String
            >> umlStateMachineNameToMachineId = new HashMap<>();
    private HashMap<String, UmlStateMachine
            > umlStateMachineIdToumlStateMachine = new HashMap<>();
    private HashMap<String, String> umlMachineIdToumlRegionId = new HashMap<>();
    private HashMap<String, HashSet<String
            >> umlRegionToState = new HashMap<>();
    private HashMap<String, HashSet<String
            >> umlRegionToTransitionId = new HashMap<>();
    private HashMap<String, HashMap<String,
            HashSet<String>>
            > umlRegionToumlStateNameToStateId = new HashMap<>();
    private HashMap<String, HashMap<String
            , ArrayList<String>>> umlRegionToStateIdTransToId = new HashMap<>();
    private HashMap<String, HashSet<String>
            > umlInterationNameToInterationId = new HashMap<>();
    private HashMap<String, HashSet<String>
            > umlInterationIdToLifeLineId = new HashMap<>();
    private HashMap<String, HashMap<String,
            HashSet<String>>
            > umlInterationIdToLifeLineNameToLifeLineId = new HashMap<>();
    private HashMap<String, HashSet<String>
            > umlInterationIdToMessageId = new HashMap<>();
    private HashMap<String, HashMap<String,
            ArrayList<String>>
            > umlInterationIdToMeaasgaIdTranse = new HashMap<>();

 

这是所有的数据结构,复杂且繁多,但都是必要的,同学告诉我了更加抽象的表示方式,由于水平有限,故写成了这样复杂的情况。

关于构造部分值得注意的是,构造函数不能产生依赖,比如类的属性不能和类本身的出现时间相关联,故只能给出该条构造信息所包含的内容,而信息与信息之间的依赖关系,需要在算法部分体现。

下面是算法部分:

  • 关于类图的查询指令

    • 模型中一共有多少个类

      直接数类的id数量

    • 类中的操作有多少个

      数每个类下的操作的id数量接口

    • 类中的属性有多少个

      同理,数类中的属性id

    • 类有几个关联

      主要是数一个类引出了多少个关联的数量,此处需要算上父类的

    • 类的关联的对端是哪些类

      同上,这两个可以在一起实现

    • 类的操作可见性

      该指令与之前构造的操作相关数据结构有关系,需要对操作在构造的时候分类

    • 类的属性可见性

      与构造属性的时候相关,在构造的时候存储好

    • 类的顶级父类

      这是一个循环,之前只能构造出父子之间的继承关系(不能产生依赖的原则),因此在此处进行循环向上搜索,得到顶级父类

    • 类实现的全部接口

      这是一个遍历搜索问题,向上搜索加入set即可,同样需要遍历所有父类

    • 类是否违背信息隐藏原则

      需要对每个类的每个属性的隐藏性进行评价,在构造的时候可以在那时候将类的隐藏信息给定,对属性的可见性进行判断后,施加给类,当然需要以某种原则施加。

  • 关于UML状态图的查询指令

    这是第二次作业的新增内容

    • 给定状态机模型中一共有多少个状态

      数状态的id,需要注意的是起始状态和终止状态也算在里面,即三种类型

    • 给定状态机模型中一共有多少个迁移

      统计迁移id数量即可

    • 给定状态机模型和其中的一个状态,有多少个不同的后继状态

      这里用bfs处理即可,需要找到所有可达状态

  • 关于UML顺序图的查询指令

    • 给定UML顺序图,一共有多少个参与对象

      数lifeline的id数量

    • 给定UML顺序图,一共有多少个交互消息

      数message的id数量

    • 给定UML顺序图和参与对象,有多少个incoming消息

      数以这个id为目标的message数量

  • 模型有效性检查

    我认为这是第二次作业最难的部分,前面的查询算法较为简单,可以看到助教超级善良的构建了最后一次作业

    • R001:针对下面给定的模型元素容器,不能含有重名的成员(UML002)

      重名比较简单,遍历即可

    • R002:不能有循环继承(UML008)

      这条比较难,需要进行dfs深搜,找路径。

      在深搜的时候我遇到了tle的问题,原因是这里是图的dfs,不是树,因此其深搜条件需要更改,否则超级慢,慢到难以想象,只要继承链长一点就会出现。

    • R003:任何一个类或接口不能重复继承另外一个接口(UML009)

      这一条是针对对接口的继承,或者加实现,首先构造的时候不能是set而应该是arraylist,否则会有一个接口继承另一个一百次,而在set里只统计一次的情况出现。

      接下来是对每个类和每个接口都遍历,分别考察其重复继承情况,比如对于类,考察其自身的实现与父类的实现,组成的set的重复性问题。接口同理可得。

以上是关于架构的所有部分,写的比较繁杂,但基本思想是对每个方法分别构建需要的数据结构相应处理。这样的优点是独立,缺点是复杂,太复杂,很多次我在写代码的时候看晕了,因此还需要学习大佬们的创作思路。

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

这是一个很有意义的话题,四个单元,每个单元三次作业,不断递进(最后一次作业除外),还加上单元结束的博客作业,这样一个流程下来,收获和感受很多很多,也学到了很多方法,体会到了很多东西。

对于架构设计而言,这是一个十分重要的话题,没有一个好的架构,是无法支撑庞大的代码,事实上每单元的最后一次作业都超过了1000行的代码量,这样的代码量下找bug是很困难的,只有一个清晰的结构才可能找到一些bug,同时写起来也会比较简单。

对于第一单元,主要考虑的是面向对象的基本思想,类是基本结构,所有事物以类为起源。多项式求导,这样一个简单的事情,在面向对象里,需要拆分成多个实体,即类。因子,表达式,项,求导规则,等等,通过这种抽象,整合,可以使得整个求导过程流程化,以至于可以处理很复杂的式子。

在第一单元中,第三次作业给我留下很深的映像,在产生递归构造之后,起初的正则表达式以无法使用,转而对其内在结构进行抽丝剥茧,从而提炼出其中的精华,最终还是递归了,但那时候的递归是解决问题的手段,通过对不同规则的处理,真正做到了面向对象的思想,事实上我认为这是我在所有作业中最面向对象的一次。

今天再次看到第三次作业的代码,仍然觉得很精妙,很精妙,我想这值得我思考很久很久了。

第二单元是多线程了,多线程上的架构无非是将线程进行单独的类,调度器与线程本身的分离。在其中,OO也就体现在如何分离,谁负责谁的问题,是每个电梯都有一个调度器,还是调度器负责了电梯,电梯只用上下呢?很显然后者更好,但对架构能力要求更高,最后我还是选择了前者,顺利完成了作业。因此我说第一单元的压轴题是我对OO理解最好的一次,那种分离的思想,只有经历过才深有体会。

第三单元是规格,完成图的构建,我顺利的完成了三次作业,但在那之中,面向对象体现的并不好,直到最后一次作业我才知道需要用图。但重要的是,从第三次作业开始,我们开始关心数据结构,开始选择,开始计算我们的代码的时间复杂度,是否会TLE等等,这些都对我们的性能提出了要求,而但最后一次作业时候,增加了换乘,增加了同建模体系下的多种dijkstra,这也是一次很精妙的故事。

拆点或不拆,在周一晚上我发现了我的程序写出了理论性bug,那时候很焦虑,因为有一些很严重的问题,最后我仍然选择了拆点,在没有大改动的情况下完成了作业,但强测炸了一个点,在连通块上出了问题。课下我对拍了很多次,事实上对拍贯穿整个OO的始终。

第三单元的面向对象,即是统一的建模语言,不同的参数,实现不同的功能。

最后一个单元,类图解析,上面也提到了,这单元纯粹用了n个数据结构,很暴力的完成了两次作业,我知道这样不好,但也不知道怎么样更好了,似乎我对OO的理解,只是拆分,分离,统一语言,那样美好的画面,而最后到实际上,确实另一个样子。

我想,之所以如此,吴际老师在最后一次大班课的时候,告诉我们,没有代码不谈结构,我不知道我体会好了没,但我的架构确实很难,保持了正确性之后,反思架构,仍然是很有必要的。但不表示在建模之前不谈架构,只是说,你能写出来,这是本事,你能写的又对又好,这是更大的本事,但不表示你写不出来,写不对,告诉我了世界上最先进的架构,这不能说明什么。

测试理解与实践的演进

测试,这也是一个贯穿始终的词语。我们说OO容错率高于计组,是这样,高于OS呢,也对。但这更多的是从分数角度而言的,对于一次作业来说,费尽心血的结晶,在强测中却是另一幅惨不忍睹的画面的时候,内心的崩溃的。我曾有过100的高潮,也有过不及格的低谷,每次作业都告诉着我,不要乱写,不要乱想,不要乱来。

只要是作业炸的时候,我都会埋怨自己,为什么这样想,为什么不好好测试,为什么会这样。但从另一种角度,在一切都结束之后,回过头来的时候,那条布满荆棘的路,自己走了过来,仍觉得感动于真实。正是因为充满苦难,才对它有了更好的理解和认识。

测试,是极其重要的,第一单元,我只是随便测测,构造一些超级简单的东西,拿中测当测试数据,结果是3次作业炸2次,呵呵。

心态血崩之后,痛改前非。第二次,多线程,这样难测试的东西,除了第一次作业,后面两次我都构造了精准输入的对拍器,很复杂,还有判断标准,这样形成的自动化测试,输入输出,结果自然是非常好的,我去翻了翻,三次都没有正确性的问题,这足以说明测试的重要性,构造大量的样例去做测试,跑一天一夜,总能出bug吧。这样测试完后,心里踏实,放心。

但是这毕竟是量的测试,精准度怎么办?果然,在第三次作业,我仍然采用对拍,缺因为数据构造太弱,部分边界没测出来,导致两次100一次95,也就是挂了一个点,这说明,更加精准的测试是很有必要的,随机数据的构造和对拍,并不一定可以得到想要的结果,并不能保证准确性,而是对代码进行覆盖的,全面的测试,才是好的。

最后一个单元,很难测试的情况下,我构造了一些很极限的样例测试,奈何,我测完之后修改了,忘了再测,于是我交的版本连我自己构造的样例都过不了,难受啊。这也告诉我一个道理,不要对自己的修改过于自信,也许哪里抽风了,改错了,自己浑然不觉,测试是一定要再测的!

课程收获

OO这门课教会了我很多,从学习到做人,从做人到生活。

很明显的,这样每周每周的压力,几乎每一周都要完成作业,还有OS的课程设计在那边,可想而知,压力是非常大的,高工今年不如6系,还有离散数学,概率统计,极其耗时的物理实验和数学建模,而这些,6系的同学这学期都没承受(离散数学除外)。在这样的环境下,我坚持每周五看指导书,想架构,拿周六周日两整天的时间写代码,找bug,我几乎每次都保证公测一开我就能交上去,但交上去并不是万事大吉。在周二晚上截止之前,我还会不断的测试,测试完,我会紧张自己进互测没,是A还是C组,我也会关心互测的情况,不断刷新。而这一切结束的周四晚上,紧张的等待着强测结果的公布,如果是好的,我会欣喜,如果不好,我只能去慢慢找bug。于是周五到了,下一次作业又过来了,这样周而复始的学习,让我感到了压力,但同时,它带给我很多很多正面的力量,我不再觉得生活的无聊,我觉得这样的生活很充实,很丰富,闲暇之余,周一周二,我会进行一下OS的编码,最终OS也顺利过了lab6,可以参加挑战任务。

OO告诉我,要加快效率,尽早学习,每次指导书下发我就会去看,而周五晚上是马原课,因此可怜马原课从没听过讲。这样的效果是我不会把它拖到周二的ddl,也就有了充分的时间去测试,去和同学们交流讨论。这种讨论,增进了同学之间的情感,在这学期之前,同学对我来说,更多是在一起学习,而这学期过后,它们真的可以帮我解决困难,不论是OO或是OS。

我们会一起探讨架构,探讨测试数据,会互相分享,互相鼓励,这种感觉,很好很好。

OO是一种生活,它不是一门课,这种强度的生活方式,可以带给人巨大的提升,上一节描述了关于架构的思想提升,这些东西,都要亲身体会才能知道的。

但与此同时,最直接的,我的编程能力也提升了,从一开始写代码,写的不太好,到现在写一千多行的代码,还不容易出现巨大的bug,这种进步是OO带来的,是显著的,它直接性的提升了我们写代码的能力,写高质量代码的能力。

立足于自己的体会给课程提三个具体改进建议

最后是建议,其实我觉得OO已经做得足够好了,足够好了,在刚开始学的时候,我感觉到了OO课程组的摆设的时间线,给我们各种压力。但后来,我都理解了,体会到了那种温情,助教很辛苦,老师很辛苦,辛苦到我们难以想象的程度。网站会崩,评测机会坏,指导书会迷惑,但助教们,老师们,一直在,正因为它们在,我们才有可能顺利完成。我也写过评测机,但我写的是多烂,只有我自己知道,我也构造过样例,想构造出能hack到人的样例,已是无比困难,何况还能构造出面向所有人,产生区分度的样例。

即便如此,我仍可以提出一些建议,或者说是一些感受,以便助教和老师们查阅。

首先,从难度设置上,我仍然认为第一单元难度过大了,我在写第一单元的时候,遇到的巨大的困难,后面都没遇到过,第一单元便是高山,这会在一定程度上挫伤同学们对OO的兴趣,我觉得是否可以优化一下课程结构和顺序呢?

其次,理论课上的内容和实验,还是有很大的差异性,即使理论课说的很好,但具体到每次作业上,我们还是很难从宏观层面把握它们。上面我也提到了我只有在第一单元第三次作业,感受到了OO思想,后面的感知,不如那次强烈,为什么会如此?是否是因为,OO作为一种思想,本身就很难呢,或是万物皆可OO,在我们眼里,仍然不还理解。

最后,优化课程评价体系,强测的占比,真的很重,但有时真的只是一点点失误,无奈强测被无限放大,假设我把一个if写错了,只用更改一行,但强测得了0分,这样的例子不是没有,我想,的确不应该以行数去分优劣,看的是你产生的影响,但强测样例和中测样例,实在差异太大。我提议能否优化中测难度,给几个强测点进去,但不作为进互测要求,这样是否可以使得学生能更好的进行测试呢。

当然我知道,老师和助教远比我看到的多,因此我只是描述一下个人感受而已。

最后,真心祝福OO这门课,越来越好,越来越好,越来越好,祝每个参与其中的人,幸福快乐!

posted @ 2019-06-21 17:55  whymi  阅读(108)  评论(0编辑  收藏  举报