• OO终章


    一.本单元作业架构设计  

    1.第一次作业

    本次作业,需要完成的任务为实现一个UML类图解析器 UmlInteraction。

    这次作业和高工的烤漆很接近,所以没有时间去考虑那么多设计构架,就按照怎么方便怎么写了。那怎么写方便呢?这个解析器首先读入UmlElememt的信息,储存起来,并计算出与一些有助于后续计算的数据。后续查询中读取它们并计算。

    所以,在读入时,把每个数据按照类型放入以id作为key的HashMap中。然后对于查询指令中的核心——类和接口,设置了自定义的类:myClass和MyInterface。对每个类,存储它的数据成员,方法成员,继承的父类,实现的接口,关联的类。对于每个接口,存储它的继承的父接口。

    每次查询操作的时候,找到对应的类,读取其包含的信息,再进行计算即可。

     

     本次作业的UML类图:

     

     

     

    2.第二次作业

    这次作业正赶上离散数学2的考试,所以构架比较随意。

    将四个需要的接口实现类使用组合的方法放在总类中,对于总类的查询指令分摊到各个组合类中,大大减少了总类的复杂度。

    UML类图的读入和查询完全继承了上次。

    顺序图的读入和查询很简单,没什么值得探讨的。

    状态图的读入也不困难,困难在于后继状态的查询。这个查询指令足足写了我两个多小时。一开始我选用DFS的想法,但是DFS对于初始节点是否可以到达自己的判定十分麻烦。最后使用了BFS,把每个状态的直接后继都遍历一遍。

    preCheck类是最为困难的一个类,我用离散数学中的图论方法加以解决。对于R001,需要和UML类图中一样,把所有的属性名和关联端点名都记录下来,检查是否有重复。对于R002,我将类和接口都作为有向图的顶点,把继承和接口实现都作为有向图的边。从一个顶点出发,DFS递归查找,如果发现当前访问的顶点就是出发的顶点,就判定为循环继承。对于R003,首先对每个类找到它在图中的所有前驱顶点。然后检查前驱顶点的list有无相同元素即可。如果有,则出现了重复继承。

     

    本次作业的UML类图:

     

     

     

     

     

     

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

    第一单元:

    1.

    这一次作业表面上用了面向对象,实则是一只披着OO的皮的面向过程编程。

    在输入后,先实例化一个多项式类对象,再进行检查(调用check方法),接着用加减号分割整个多项式成为一个个项装入ArrayList中(调用create方法),然后计算每个项的导数放入一个新的ArrayList中(调用cal方法),最后用zhengli方法一把输出。

     DuoXiangShi类是整个工程的核心,各种方法上文也提及了。Xiang类只有存放和取出系数和指数两种功能,实际效果等同于C语言的结构体。Dealer这个类说来尴尬,是DuoXiangShi类的create方法写完之后超过了checkStyle规定的长度,于是偷懒找了个类扔进去部分代码(很不好的设计风格,在后两次作业中改正了)。由此导致DuoXiangShi类和Dealer类耦合极其严重。

    2.

    这次作业的代码构架几乎是第一次作业的复刻。所以没啥好多说(复读)。细节上的变动在于对Xiang的成员变为了4个(一个系数和三个幂次)。在DuoXiangShi中增加了处理三角函数的相应功能。所以耦合度高的问题依然存在。更重要的是,这仍然是一个披着OO外皮的面向过程程序。

    3.

    这次作业改过自新,采取了真的面向对象(至少是我这样的弱鸡理解的面向对象)的编程思想。算法采用的是递归下降(最大的优点在于可以甩锅)。

    对于输入输出单独建立InOut类,在其中建立一个DuoXiangShi对象后就把输入扔给它,让它计算就OK了。

    对于DuoXiangShi,要进行表达式是否合法的检测,然后根据加减号把多项式切成一个个单项式,建立DanXiangShi对象然后喂给它就行啦~

    对于DanXiangShi,依然要进行合法性检测,然后乘号把单项式切成一个个因子,建立YinZi对象然后丢给它。

    对于YinZi对象,就是要区分各种因子的类型(有Sin,Cos,MiYinZi,ComlexSin,ComlexCos,Comlex共六种),然后建立各自的因子对象就行啦。

    前三种因子分别是不嵌套的sin和cos以及幂函数,就直接求导,给出Output就行啦。

    后三种因子就需要递归调用DuoXiangShi,然后利用嵌套函数求导公式求出导数。

    整个过程是根据递归的思路做的,尽量在类和类之间不直接调用而是采用消息传递的机制。两个类之间都只有构造器和output方法需要交换一次字符串,所以耦合度不高。每个模块各自对应一个极其清晰的功能,所以内聚度也不错。

    这次作业代码总量接近1000行,是我接触计算机以来写过的最大规模的工程。说实话,我真的难以相信自己可以管理这么大规模的工程。而且开发实际上只用了3天(半天想算法,一天半写代码,一天debug)。整个工程虽然庞大,但是组织结构清晰,所以bug定位非常容易。

     

    第二单元:

    1.

    这次作业完全是生产者——消费者模型的翻版。所以设计起来可以说是照搬照抄。我设计了Input类和Elevator类分别作为生产者和消费者,用Controllor(这是个拼写错误)作为共享对象。Input类每次读入请求,交给Controllor,放入一个线程安全的队列中。Elevator类在一个while循环中不断去Controllor中的队列中读取请求(如果读取不到就sleep50ms)。对于ctrl+D的结束输入呢?我采用了这样的思路:Input类可以检测读入是不是null,如果读入null,就将共享对象中的一个flag置为true并且自身break,每次电梯去访问共享对象中的请求时候,都会检测这个flag,如果flag==true,就直接break。

     

    2.

    第六次作业=第五次作业+实现捎带功能。所以本次作业的设计的一开始,我想和上次一样,使用Input类读入输入,放入Controllor类,并由Controllor类实现对电梯Elevator类的调度。而Elevator本身不具备任何“思考”的能力,即只实现开门、关门、移动等“机械”的能力。但是随后实现中(代码写完了之后)我发现一个重要的问题:Controllor类由于需要获知电梯在哪里、处于开关门状态等大量电梯的“机械”属性,导致Controllor和Elevator之间的耦合度极其之高。思索再三之后,为了实现“低耦合”的设计思想,我直接重构了代码。

    重构后的构架仍然使用Input类读入输入,放入Controllor类中的一个队列中。Controllor只有3个主要的方法,put(放入请求),getMain(取得主请求),getSub(取得捎带请求)。而交由Elevator类来实现“是否捎带”、“如何捎带”等问题。这样的构架虽然和日常生活相违背(生活中是调度器控制电梯,而不是电梯控制它自己),但是有效降低了类之间的耦合度。所以仍然是一种可行的设计。

     

    3.

    采用了两级流水调度器的结构。第一级为Controllor,分配请求给各个电梯。第二级为Elevator,调配电梯内的请求执行顺序。首先为了方便对于请求的处理,我写了一个Request类来代替PersonRequest类,主要区别在于多了一个变量来标记请求是否可以被执行(有的请求因为乘客还没到达fromFloor而无法被之执行)。又由于上次作业代码构架良好,本次作业大量继承了上次作业的代码,实现了良好的复用。Input类和Elevator类几乎没有变化,Controllor类中提供的主要方法依然是put(),getMain(),getSub(),功能分别为得到请求,取出主请求和取出附属请求。但put()方法中对于那些不能直接到达的请求做了拆分,统一安排到1楼或15楼进行换乘,拆分为两次可以直接到达的请求。有一个值得注意的细节是:对于换乘中的第二步,需要标记它暂时不能进行,知道第一步换乘完成后,才能标记它可以进行。getSub()方法中还需要增加对于电梯内人数的检测。

     

    第三单元:

    1.

    这次作业很简单,就是按照JML写就行。唯一需要注意的就是数据结构要用好,使查找和计算尽量O(1).

    采取的措施例如:

    MyPathContainer类没有适用JML中给的静态数组的数据结构,而是使用了idToPathMap和pathToIdMap这两个HashMap,分别储存路径id到路径,路径到路径id。这样的好处是,从一者查询另一者就不需要遍历查询,而是直接O(1),提升了效率。

    其中nodeCount这个hashmap数据结构,储存了Node到路径出现次数,是专门为实现查询容器全局范围内不同节点的数量的。在add和remove维护nodeCount,在调用getDistinctNodeCount方式时候就可以直接O(1)查询,大大减少了非变更性指令的执行时间。

    public /*@pure@*/int getDistinctNodeCount()  //在容器全局范围内查找不同的节点数
    {
    return nodeCount.size();
    }

    2.

    本次作业中MyPath类没有变化,在MyPathContainer类基础上写了MyGraph类。为什么不继承呢?因为checkstyle不允许适用protected权限的变量,所以继承就无从谈起!这个为了规范代码风格的插件居然影响了OOP最重要的继承特性,真是让人失望。

    言归正传,本次作业最核心也是最困难的函数就是计算最短路径,可以计算最短路径,也就容易判断是否连通了。

    所以,我设计了两个静态二维数组:countMatrix用来记录每条边出现的次数,disatanceMatrix用来记录点和点之间的最短路径。每次增/删边的时候,更新countMatrix,再根据countMatrix,适用Floyd计算各个点之间距离,写入disatanceMatrix(对于不连通的点赋值为999999)。

    在判断是否连通时,判断路径长度是否>250,>250就是不连通。

    在计算最短路径时,直接读取disatanceMatrix,复杂度为O(1).

    此方案中还有一个有意思的设计。读入的nodeId和disatanceMatrix,countMatrix中数组下标并不一致,但是存在一一映射关系。所以,我借鉴OS中空闲物理页表和虚拟页表分配的想法,建立了“空闲数组下标链表”:availbleIndexList。当增加新Node时从中poll一个空闲数组下标,删除Node时候又将其释放后放入availbleIndexList。

    3.

    这次作业的依然没有采用继承的良好设计(还是因为checkstyle)。所以在图中可以看出,MyRailwaySystem类异常冗长。这确实非常无奈。

    此次作业我采用了讨论区中”权转换“的方法(完全相同)

    方法参见:https://course.buaaoo.top/assignment/75/discussion/213  

    这位老哥的方法非常之强,把动态权图问题转化为静态权图问题。所以依然可以用Floyd快乐暴力解决。与第二次作业中记录最短路径的数组类似,本次作业新增了这三个数据结构:

    private int[][] matrixTransfer = new int[120][120];     //记录最短换乘
    private int[][] matrixPrice = new int[120][120]; //记录最少票价
    private int[][] matrixUnpleasantValue = new int[120][120]; //记录最少不满意度

    对于matrixTransfer的更新十分简单:直接遍历现有的所有path,每条path都赋值。一条path上赋值为1,注意其中相同节点赋值为0。

    对于matrixPrice和matrixUnpleasantValue的更新比较麻烦:对于所有path,先要在本path内赋值完相邻节点后Floyd。最后所有path赋值完成后,再次全图Floyd。

    对于计算连通块getConnectedBlockCount(),我用了BFS:从一个节点出发广度优先遍历,能找到的点都设置为“已访问”,计数器+1。再在未访问的点中选一个点,重复以上过程,直到所有点都已经被访问过。计数器的值即为连通块数。

    在构架设计上,我从一开始的面向过程,到后来逐步主动使用面向对象的思想解析问题需求,这就是我在设计上最大的思想上的进步。还有就是主动的思考并尝试使用各种设计模式,有时候还会在一个工程中各个实现部分中使用多个设计模式。最后,从设计一开始,而不是写完代码,就开始考虑程序中可能存在的bug,也是在实践中得到的重要经验。

     

     

    三.测试理解与实践演进

    第一单元:手工编造数据

    第二单元:使用自搭建的测评机

    第三单元:使用自搭建的测评机+Junit4

    第四单元:使用自搭建的测评机+手工编造数据

    说实话,关于测试方面,我没有花费太多心思。主要是可以通过跑(大佬搭建的)自制测评机,还有中测数据,基本上就可以发现所有的bug(在第2,3,4单元强测都没有发现bug)。

    其实我觉得利用良好的设计避免bug才是正道,而不是测试发现bug然后debug。(要不然bug是de不完的

     

     

    四.课程收获

    1.从一个只能写100-200行短小程序,而不能组织工程的菜鸡蜕变为一个可以熟练管理1000行工程,勉强管理2000行工程的菜鸡。

    2.极大提升了我的自信心,把上学期在祭祖课上留下的对于计算机专业课的阴影完全消除了。这门课在一开始,我没有其他同学那样的能力(尤其在第一单元),但是到了最后两个单元,我就已经轻车熟路,甚至还做了一次课上展示,并获得了博客分享奖。在OO课的这一个学期,我从缺乏自信,到能够相信自己的能力不比其他人差,甚至在有些方面比别人强。这是面向对象课带给我的最大收获。

     

     

    五.改进建议

    1.实验课往往是上午刚学完,下午就要考试了,知识点也没有太熟悉,就要做题,经常感觉课上写的很懵。建议可以增加实验课的解答和讲解环节。

    2.互测屋内要测的人数太多了。根本没有办法认真看其他人的代码,只能用自己搭建的测评机跑别人的程序,其实对自己没啥帮助。因此建议减少互测屋人数。

    3.上课内容和作业内容有时候关联不够强。比如多线程的课上讲的各种避免多线程的方法,实际作业中都只用了synchronized这种最简单的方法。希望能够在课上多讲些和作业相关的内容,尽量不要仅仅讲授理论知识点。

  • 相关阅读:
    JNDI 是什么
    RuntimeException和非RuntimeException的区别
    dynamicinsert,dynamicupdate能够性能上的少许提升
    Session,有没有必要使用它?[转]
    c# textbox中光标所在行命令及选中命令移动到最后一行且光标提前[转]
    C#分布式事务(TransactionScope )
    .net中的分布式事务
    大道至简,职场上做人做事做管理[转]
    C#中TreeView的CheckBox的两种级联选择
    C# winform TreeView中关于checkbox选择的完美类[转]
  • 原文地址:https://www.cnblogs.com/gagaein/p/11067277.html
Copyright © 2020-2023  润新知