OO第四单元

一、作业架构设计

  • 第一次作业

作业需求

实现一个UML类图解析器,可以通过输入各种指令来进行类图有关信息的查询。

难点:

CLASS_ASSO_COUNT,CLASS_TOP_BASE,CLASS_IMPLEMENT_INTERFACE_LIST

架构设计:

类图中的元素有明显的层次性,根据层次重写三个类,对应Class,Interface,Operation。

 

MyClass属性,MyInterface类似。

public class MyClass extends MyElement implements MyObject {
  
private HashMap<String, MyOperation> idOpMap = new HashMap<>();              //Operation id->MyOperation private HashMap<String, HashSet<MyOperation>> nameOpMap = new HashMap<>();       //Operation name->MyOperation private HashMap<String, UmlAttribute> idAttriMap = new HashMap<>();            //Attribute id->UmlAttribute private HashMap<String, HashSet<UmlAttribute>> nameAttriMap = new HashMap<>();     //Attribute name->UmlAttribute private MyClass father = null;                                  //Father MyClass private HashSet<MyInterface> interfaceRealizationSet = new HashSet<>();         //Interface MyInterfaceprivate HashSet<MyClass> assEndSet = new HashSet<>();                    //AssociationEnd MyClass }

MyOperation属性

public class MyOperation extends MyElement {
    private Visibility visibility;                                  //Visibility
    private HashMap<String, UmlParameter> idParaMap = new HashMap<>();            //parameter(in)
    private UmlParameter returnp = null;                              //parameter(return)
}

实现思路:

    1. 由于不保证元素出现的顺序,先遍历元素并分类。相当于给每个uml元素准备一个口袋,按照类别放到各自的口袋。
    2. 遍历Class和Interface对应的口袋,生成对象。这两种对象类似一个容器,通过add方法,添加自己的方法,属性,或者关联。
    3. 再遍历Operation,Attribute,Association,Generalization,Realization,添加到对应容器。
    4. 实现查询指令,这里只探讨刚才提出的三个难点。他们有一个相似的解决思路——dfs。比如CLASS_ASSO_COUNT的计数函数,先调用父类的函数,返回父类Association的集合,再自己的Association合并。CLASS_IMPLEMENT_INTERFACE_LIST指令应该注意,既要有父类实现的接口,又要有自己的接口继承的父接口。
    5. 应用缓存思想:把查询过的结果储存好,接到指令先判断是不是有存储的结果。
  • 第二次作业

作业需求:

    1. 扩充状态图和顺序图的查询指令。
    2. 对类图进行模型有效性检查。

难点:

检查循环继承,检查重复继承;查询一个状态的后继。

架构设计:

有四个功能相对独立的接口:

UmlClassModelInteraction

UmlCollaborationInteraction

UmlStateChartInteraction

UmlStandardPreCheck把方法实现在一个类里面代码会超长,功能也会十分混乱。应该写四个单独的类来实现方法,在主类里复用这些方法。代码复用有两个途径,一是继承,二是在类里创建对象,我选择了后者。

 

状态图和顺序图采取了和类图相似的层次性架构。我还写了一个有向图的类(Graph),封装求解可达节点的算法。这是状态图的结构。

 

实现思路:

  1. 循环继承:类是节点,继承关系是有向边,从任意一个类开始dfs,记录经过的路径,把路径传入更深一层的递归。如果新遇到的类已经在路径里出现过,说明在这条路径上,从这个类上一次出现到这一次出现之间的节点形成一个环,把环上的节点加入一个集合。从剩下类中挑出一个不在该集合内的类,继续dfs,直到所有类要么遍历过,要么出现在集合里。集合里就是循环继承的类。
  2. 重复继承:接口重复继承接口,对于一个接口,先dfs判断所有的父接口如果有重复继承,那这个接口也重复继承;再判断父接口继承的接口集合之间有没有交集,有交集则重复继承。类重复实现接口,对一个类,先dfs判断父类如果重复实现,或接口重复继承,那这个接口也重复继承;再判断父类实现的接口集合和接口继承的接口集合之间有没有交集,有交集则重复继承。

二、四个单元中架构设计的演进  

  • 第一单元:带三角函数嵌套的表达式求导。

我的架构是给每一种运算写一个类,回看代码有很多面向过程的写法,几个运算类之间的联系比较混乱。代码复用性很不好,后两次作业全部重构了。第三次作业难度激增,由于对嵌套运算的不合理递归和for循环,很多点都超时了。这是因为我过于看重架构实现,忽略了算法选择,按照自己的方法一写到底。

  • 第二单元:多线程电梯调度。

这个单元我认识了几种设计模式:生产消费者模式,工厂模式,单例模式。电梯问题中的调度器是单例模式,考虑的是代码安全性;三个主要的类构成生产消费者模式,电梯是消费者,请求输入是生产者,调度器进行请求分配。第一次的分配算法用了傻瓜电梯的先到先服务方法,第二三次是主请求的捎带方法,第三次还需要考虑换乘问题。我前两次作业的复用性很好,第三次作业写的飞快,正确性也有保障。我总结出提高前期代码拓展性的方法是,架构设计越清晰越好,函数职责越简单越好,线程的交互越少越好。比如电梯的第二次作业,所有分配任务都是调度器完成的,电梯只需要上下楼开关门,并从调度器内部的一个队列里取出第一个的请求。第三次作业增加到三部电梯,变化只在调度器现在需要维护三个请求队列,三架电梯从各自队列取请求,电梯类不用修改。

  • 第三单元:JML规格。

JML相比自然语言优越在其简洁性,逻辑性和无二义性。熟练掌握JML是第三单元的学习目标。

这次作业的类也是层次化的,节点和边是最底层的类,上面一层是Path类,最上面的RailwaySystem类。另外封装一个计算最短路径的算法类。三次作业的区别就是图该怎么建的问题,第三次引入换乘问题,我使用了拆点法。如果数据结构忘的差不多了,这个单元就该还债了。最短路径算法有dijkstra和folyd可选,我三次作业一直沿用了dij算法,没有堆优化的dij是没有灵魂的dij,写着没有灵魂的dij我几乎T了第三次强测所有点。分析算法的复杂度应该成为我的一个习惯,不能只满足于通过中测,应该持续探索优化的空间。

  • 第四单元:解析UML图。

这次我理解源码和指导书花费了大量时间,代码实现起来反而简单,类的结构是高度层次化的。顶层类一级级调用底层类的函数,完成一个查询功能。在思考了几种算法的可行性之后,仍然选择了最开始想到的dfs。这两个单元的作业复习了图结构的很多经典算法,包括最短路径,可达节点,寻找环路,寻找根节点。

我注意到一个规律,对原始数据的预处理方式,严重影响后续实现的复杂程度。第三次作业搭建地铁路线的时候,如果每新加一条路径,都添加到现有的图上会很复杂,重新建图反而简单,时间也不会耽误太多。第四次作业对于所有UML元素,第一次遍历先将元素分类,再逐类处理更简单。

三、四个单元中测试理解的演进

从构造方法分类,分为人工构造测试集和自动生成测试集。从测试目的分类,分为正确性测试和压力测试。

第一次作业是表达式求导,适合人工构造和自动生成相结合。先人工构造一些特殊的点,比如WRONG FORMAT的个例。然后自动生成复杂嵌套的测试点,测试求导的正确性。构造极端数据点进行压力测试也是必要的,可以揪出爆栈问题。

第二次作业是多线程电梯,最适合对拍,用随机生成的一些电梯请求测试。

第三次作业是JML规格,认识了JUnit插件,可以自动生成一些边界条件上的测试用例。其他测试方法无法相比的一个优点是,JUnit可以针对每一个方法测试,这样发生了错误更好定位。TLE是这次作业最大的问题,构造压力测试很重要。

第四次作业是UML图查询,需要用staruml画图生成测试数据。画图要考虑全面所有情况,比如循环继承的继承环应该有所交叠,单继承多继承都出现。

四、总结自己的课程收获

  1. 从面向过程转变成面向对象。
  2. 获得了完成千行级代码的工程能力。
  3. 写出有拓展性的代码,方便功能拓展。
  4. 用插件分析代码复杂度。
  5. 自动生成测试用例。
  6. JML规格描述功能实现。
  7. UML图整理架构思路。
  8. 寻找算法资料,优化设计的能力。

五、改进建议

  1. 理论课增加JAVA易混淆知识点的讲解,少一些工程思想的介绍。十二次写代码作业把我们的实践能力练习的很充分,但知识上有很多漏洞。类继承和接口实现有很多繁琐的知识点我们始终没有触及,我在几次作业中应用到的继承也是特别简单的继承结构。有时候架构不好也不是思考的少,而是从练习中学习的这种教育模式,让我们只有用到了才去查,浅尝辄止,基础没有巩固,不如老师提前点播一下。
  2. 上机课的题目应该公布出来,有些课上没有完成的同学,之后还能继续研究。感觉我每次上机都是慌里慌张,从来没有时间好好研究一下上机的那些问题。上机过去就过去了,也没有条件细想,收获很少。
  3. 在每个单元的第一次作业时就公布三次作业的大致需求。我完成第一次作业时,不知道该怎么设计架构才能方便后面的拓展,因为不知道还要增加哪些功能,提高拓展性就是盲猜。有时候写一个性能最优但实现复杂的算法,反而是最不好拓展的。
原文地址:https://www.cnblogs.com/mollygarden/p/11073844.html