一、三次作业简单介绍
第十三次作业:
实现一个 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、架构构思:
第一次作业主要是继承 UmlInteraction 接口,实现相应的方法。
我的设计是首先在 MyUmlInteraction 进行构造,读取所有的元素,然后在 Find 里用 HashMap 进行存储, MyAssociation 、 MyInterfaceRealition 和 MyGeneralization 等直接查找好相应的类和接口,直接保存,方便查找。
同时在 Cahce 里设置缓存,记录重复信息和每次查询后结果的存储,保证不做重复查找,提高效率。
Realiton 负责继承、接口实现、关联这些关系的存储。
然后 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;
类似的, MyInterface 和 MyOperation 也是一样。
2、项目分析:
类图分析:
度量分析:
3、自我总结:
从 Metrics 的分析来看,这次作业的复杂度飘红是在 MyUmlInteraction.buildMap 和 MyAssociation.add 。
MyUmlInteraction.buildMap 是构造函数的一部分,我用了一个 for 和 switch 结构进行分类,提高了复杂度。
for (UmlElement uml : elements) {
Find.addId(uml);
switch (uml.getElementType()) {
case UML_CLASS:
Find.addClass(uml);
break;
case UML_INTERFACE:
Find.addInterfaceId(uml);
break;
case UML_GENERALIZATION:
generalizationList.add((UmlGeneralization) uml);
break;
case UML_INTERFACE_REALIZATION:
Relation.addInterfaceRealition(uml);
break;
case UML_OPERATION:
operationMap.put(uml.getId(), new MyOperation(uml));
break;
case UML_ATTRIBUTE:
attributeList.add((UmlAttribute) uml);
break;
case UML_PARAMETER:
parameterList.add((UmlParameter) uml);
break;
case UML_ASSOCIATION:
associationList.add((UmlAssociation) uml);
break;
default:
break;
}
}
而 MyAssociation.add 是因为我分别判断了两个 End 相同和不同的情况,每个情况又去添加相应的元素。
if (!contains(id1)) {
associationEnd.put(id1, new HashSet<>());
}
associationEnd.get(id1).add(Find.getById(a.getEnd2()));
if (!contains(id2)) {
associationEnd.put(id2, new HashSet<>());
}
associationEnd.get(id2).add(Find.getById(a.getEnd1()));
if (id1.equals(id2)) {
if (Find.containsClassId(id1)) {
if (!containsClass(id1)) {
associationClass.put(id1, new HashSet<>());
}
associationClass.get(id1).add(id1);
}
} else {
if (Find.containsClassId(id1) && Find.containsClassId(id2)) {
if (!containsClass(id1)) {
associationClass.put(id1, new HashSet<>());
}
associationClass.get(id1).add(id2);
if (!containsClass(id2)) {
associationClass.put(id2, new HashSet<>());
}
associationClass.get(id2).add(id1);
}
}
其他方法的复杂度都得到了有效的控制。
4、程序 bug 分析:
这次出现了一个 bug,是因为我在存储 class 的时候,直接将同名的 class 覆盖了,导致当查询子类继承父类的属性时,查找到的那个类不是它真正的父类,后来我改为用 id 查找就消除了这个 bug。
第十四次作业:
1、架构构思:
本次作业是在上次作业的基础上进行扩展,UmlGeneralInteraction 接口继承了四个接口,分别是 UmlClassModelInteraction, UmlStandardPreCheck, UmlCollaborationInteraction, UmlStateChartInteraction。我将它们分别继承,实现各种的功能后再进行整合,其中:
UmlClassModelInteraction 放入 classmodel包中,该包即上次作业的结构。
UmlStandardPreCheck也放入 classmodel,并为它设置相应的 function,即 UmlRule。
UmlCollaborationInteraction 和 UmlStateChartInteraction 分别放入新包 collaboration 和 statechart 中,并与上次作业相同,将方法归到各种的结构中,直接调用相应结构的方法。
2、项目分析:
类图分析:
度量分析:
3、自我总结:
这次作业中的飘红主要是 UmlRule.checkClass、UmlRule.checkCycle
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),所以复杂度完全可以接受。