BUAA_OO第四单元总结性博客作业

一、三次作业简单介绍

第十三次作业:

实现一个 UML 类图解析器 UmlInteraction

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

实现指令:

模型中一共有多少个类 CLASS_COUNT
类中的操作有多少个 CLASS_OPERATION_COUNT classname mode
类中的属性有多少个 CLASS_ATTR_COUNT classname mode
类有几个关联 CLASS_ASSO_COUNT classname
类的关联的对端是哪些类 CLASS_ASSO_CLASS_LIST classname
类的操作可见性 CLASS_OPERATION_VISIBILITY classname methodname
类的属性可见性 CLASS_ATTR_VISIBILITY classname attrname
类的*父类 CLASS_TOP_BASE classname
类实现的全部接口 CLASS_IMPLEMENT_INTERFACE_LIST classname
类是否违背信息隐藏原则 CLASS_INFO_HIDDEN classname

第十四次作业:

在上次作业基础上,扩展解析器,使得能够支持对 UML 顺序图和 UML 状态图的解析,并能够支持几个基本规则的验证。

实现指令:

给定状态机模型中一共有多少个状态 STATE_COUNT statemachine_name
给定状态机模型中一共有多少个迁移 TRANSITION_COUNT statemachine_name
给定状态机模型和其中的一个状态,有多少个不同的后继状态 SUBSEQUENT_STATE_COUNT statemachine_name statename
给定UML顺序图,一共有多少个参与对象 PTCP_OBJ_COUNT umlinteraction_name
给定UML顺序图,一共有多少个交互消息 MESSAGE_COUNT umlinteraction_name
给定UML顺序图和参与对象,有多少个incoming消息 INCOMING_MSG_COUNT umlinteraction_name lifeline_name
R001:针对下面给定的模型元素容器,不能含有重名的成员(UML002)
R002:不能有循环继承(UML008)
R003:任何一个类或接口不能重复继承另外一个接口(UML009)

关键点 1 —— UML 规则:

准确理解 UML 规则,然后使用 Java 来实现相应的接口。

关键点 2 —— 架构设计:

  第一次作业和第二次作业紧密相关,要考虑好可扩展性。

二、UML 设计

三、两次次作业的类图与度量分析

度量分析标识:

    • ev(G)  本质复杂度
    • iv(G)   设计复杂度
    • v(G)   循环复杂度
    • OCavg  平均方法复杂度
    • OSavg  平均方法语句数(规模)
    • WMC  加权方法复杂度
    • v(G)avg 平均循环复杂度
    • v(G)tot 总循环复杂度

第十三次作业:

1、架构构思:

BUAA_OO第四单元总结性博客作业

  第一次作业主要是继承 UmlInteraction 接口,实现相应的方法。

  我的设计是首先在 MyUmlInteraction 进行构造,读取所有的元素,然后在 Find 里用 HashMap 进行存储, MyAssociationMyInterfaceRealitionMyGeneralization 等直接查找好相应的类和接口,直接保存,方便查找。

  同时在 Cahce 里设置缓存,记录重复信息和每次查询后结果的存储,保证不做重复查找,提高效率。

  然后 MyUmlInteraction 中的每条指令都归类到相应的结构中,互不影响,例如:

getClassOperationCount Find.getClassByName(className).getOperationCount(queryType)**
getClassAttributeCount Find.getClassByName(className).getAttributeSize()
getClassOperationVisibility Find.getClassByName(className).getOperationVisibility( operationName)
getTopParentClass Find.getClassByName(className).getTopFatherName()

  其中 Find.getClassByName(className) 得到是 MyClass ,在这个类中包含相应的属性:

private UmlClass umlClass;
private HashMap<String, UmlAttribute> attributeNameMap;   
private HashMap<String, MyOperation> operationIdMap;    
private HashMap<OperationQueryType, Integer> operaCountMap;
private HashMap<String, HashMap<Visibility, Integer>> operaVisiMap;
private LinkedList<MyClass> fatherList;
private LinkedList<AttributeClassInformation> attributeInformationList;
private HashSet<String> interfaceIdSet;
private int assoicationCount;
private HashSet<String> assoicationIdSet;

​ 类似的, MyInterfaceMyOperation 也是一样。

2、项目分析:

类图分析:

度量分析:

BUAA_OO第四单元总结性博客作业

3、自我总结:

  这次作业的复杂度问题主要就是 MyPath.compareTo()Mypath.equals(),这两个方法的 ev(G) 都在 Metrics 中出现了飘红。

  然而这个问题也是没有办法的,equals() 中我们可以先检查 hash 是否相等,如果相等再进行循环检查。

  而 compareTo() 则只能一股脑的检查下去,判断大小。

  所以循环是没有办法避免的。

  而 MyPathContainer.addPath()v(G) 虽然没有飘红,但是复杂度最高的原因在于,我将 getDistinctNodeCount() 分摊到了每一次 addremove

1 for (Integer pathId : path) {
2     if (distinctNode.containsKey(pathId)) {
3         distinctNode.put(pathId, distinctNode.get(pathId) + 1);
4     } else {
5         distinctNode.put(pathId, 1);
6     }
7 }        

  其他方法的复杂度都得到了有效的控制。

4、程序bug分析:

    这次作业比较简单,我的中测提交主要是不断简化程序,降低算法复杂度。我是严格按照 JML 规格编写的,所以并没有出现 bug

5、性能分析:

  一方面我将 getDistinctNodeCount() 这一耗时利器分摊到了每一次 addremove ,减少时间消耗。

  另一方面我从讨论区中张万聪大佬的帖子中受教,根据id和路径双向索引,使用 HashMap 双容器,一个是 HashMap<Integer, Path> ,一个是 HashMap<Path, Integer>,增删时同时考虑双方,查找就可以根据不同索引进行选择,保证了性能。

第十四次作业:

1、架构构思:

  本次作业的架构概况起来就是“大胆继承,小心重构”。

  因为 Graph 接口是继承了 PathContainer 接口的,所以我的 MyGraph 直接继承了上一次作业的 MyPathContainer

  从指令上来看,上一次作业主要是涉及点结构,而第二次作业的重点在边结构。在上一次作业中,我将 getDistinctNodeCount() 这一耗时利器分摊到了每一次 addremove ,那么针对这一次作业,我需要重写 addPath()removePath()removePathById() ,继续将每一次的增加和删除操作分摊下去。

  同时设置 isModify 变量作为图结构的更新的标志。

  而针对新的指令扩展,因为“本次由路径构成的图结构,在任何时候,总节点个数(不同的节点个数,即 DISTINCT_NODE_COUNT 的结果),均不得超过250”,所以我采用静态数组,将节点离散化,把它们映射到[1,250]。

  针对最短路径,使用 Bfs 算法,采用 cache 机制,将每次计算目标结点之间距离时经过的中间结点的距离都保存下来,如果 isModify 被置 true,则清空 cache,重新计算。

2、项目分析:

类图分析:

度量分析:

3、自我总结:

  这次作业中的飘红主要是 graph.MyPath.compareTo()、graph.MyPath.equals()、graph.MyGraph.bfs()graph.MyGraph.cache(),其中 graph.MyPath.compareTo()graph.MyPath.equals() 在上次作业中已经分析过了,并没有办法降低 ev(G)。

  bfs() 属于标准算法,而 cache() 中牵扯到一个图的重构,这两个方法我在第十一次作业中进行了再一次重构,将复杂度降低了下去。在本次作业中的 cache() ,我是这样写的:

 1 for (int i = 0; i < super.getLength(); i++) {
 2         for (int j = 0; j < super.getLength(); j++) {
 3             if (i == j) {
 4                 renewGraph[i][j] = 0;
 5         } else if (graph[i][j] == 0) {
 6                 renewGraph[i][j] = inf;
 7         } else {
 8                 renewGraph[i][j] = 1;
 9         }
10     }
11 }        

然而实际上只需要修改一下 bfs() ,在需要的时候判断一下,就能省去这个 O(n2) 的方法。当时可能是脑子轴了,没有想到。

4、程序bug分析:

这次作业我的强测爆的极其惨烈,原因在意“小心重构”的“小心”二字我没有做到。

bug 出现在 MyPathContainer.removePath()MyPathContainer.removePathById()。

为了将图的更新均摊到每一次 remove 操作,最初我是这样写的:

 1 if (distinctNode.get(pathId) == 1) {
 2     distinctNode.remove(pathId);
 3     removeList.add(removeId);
 4     mapping.remove(pathId);
 5 
 6     for (int i = 0; i < length; i++) {
 7         if (graph[removeId][i] != 0) {
 8             graph[removeId][i] = 0;
 9             graph[i][removeId] = 0;
10         }
11     }
12 } else {
13     distinctNode.put(pathId, distinctNode.get(pathId) - 1);
14         if (graph[removeId][prevId] != 0 && removeId != prevId) {
15             graph[removeId][prevId] = 0;
16             graph[prevId][removeId] = 0;
17     }
18 }

  每一次删除操作,我都将这个 Path 中的节点的所有边全部删除,如此错误的写法我当时居然没发现,实属不应该。

  因为这个头昏的写法,我的强测炸的妈都找不着了。

5、性能分析:

  性能方面,MyGraph 继承了上一次作业的 MyPathContainer ,相关方法的复杂度得到有效控制。

  而本次作业相关的图操作,除了我上文提到的 cache() 的简化问题,bfs 最多跑 20×n 次,每次复杂度O(V + E),所以复杂度完全可以接受。

四、架构设计及OO方法理解的演进

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

六、课程收获

七、给课程提三个具体改进建议

上一篇:BUAA OO 2019 第三单元作业总结


下一篇:BUAA_OO第三单元总结性博客作业