一、架构分析
两次UML解析程序,主要是递进式的,第一次解析了类图,第二次在类图基础上,增加检查规则和顺序图及状态图。
总体架构思路主要有五点:
1、尽量还原图本身的结构(组装)
2、分类(状态图、顺序图、状态图)
3、元素与关系分离
4、对同一类element进行合并
5、抽象Element
先从类图来讲:
1、还原
每个class有attribution、operation
每个operation有parameter
因此有
UMLPARAMETER-->UMLOPERATION + UMLATTRIBUTION -------> UMLCLASS
对接口有类似操作,继承Class即可
简单的画个类图他们关系大概就是这样
我们只需通过json中的_parent和id就可以把他们联系这一起,至此重组完成。
针对第二次解析程序这里有一些小改变(元素重名检查):
由于类图检查需要检查Class对端的asscociationEnd与自身Attribution的重名问题。也就是说associationEnd也会属于Class,因此AbstractClass中的内容需要增加asscociationEnd的存放(只需增加一个容器和规则检查方法,整体难度不大,算是做了一次小拓展)
2、关系与元素分离
关系主要是类之间的,有继承、实现、关联。
由于UMLClass是我们元素组装的最上层,自然关系与其同层次,放在任何一个里面都不太合适,因此选择了将他们分离的策略。
我们单独建立一个Relation来存放他们。
解析json-->识别type-->分类存放 即可。
针对第二次解析程序这里有一些小改变(关系检查):
无论是循环继承的检查还是重复继承的检查,都整合了这两种关系(继承、实现),因此为了操作方便,这里选择增加一个RelationCheck类,构建一个继承和实现关系构成的图。然后用上各种搜索方法(如DFS)就可以轻松解决上述检查。
由此可以看出,在这种架构之下,两次解析程序的递进针对类图都不会出现重构的情况,只需做少量拓展,即可完成。
再讨论下状态图吧
1、还原
跟类图一样,按照层次关系:(关系比类图要简单点)
UMLState + UMLTransition --> UMLReion --> UMLStateMachine
重组即可
public class AbstractStateMachine {
private String name;
private String id;
//Region
private String regionId;
// UMLTransition
private HashMap<String, UmlTransition> idToTranstion = new HashMap<>();
private HashSet<AbstractTrans> transHashSet = new HashSet<>();
//UMLState
private HashSet<AbstractState> states = new HashSet<>();
private HashMap<String, AbstractState> idToState = new HashMap<>();
private HashMap<String, ArrayList<String>> nameToState = new HashMap<>();
//RELATION
private HashMap<AbstractState, HashSet<AbstractState>> transRelation
= new HashMap<>();
private HashMap<AbstractState, Integer> idToSubCount
= new HashMap<>();
可能会发现Region怎么似乎和State平级,主要是由于Region实在对查询功能无大用,更像是StateMachine的傀儡???
2、合并
这是我针对本次作业想重点讨论的话题。
也是我认为本次设计成功的点之一:为了易修改,易拓展而设计
State
状态机中有三类状态:UmlFinalState、UmlPseudostate、UmlState,状态统计需要考虑这三者,同时状态转移也是在这三者中进行,因此我们可以采用一个AbstractState类来代替这三种。
public class AbstractState {
//0: state -1:init 1:final
private int type;
private String name;
private String id;
public AbstractState(int type, String name, String id) {
...
}
这样做有良好的可修改性,我们可以自行定义,什么是共同状态?。
比如针对之前一直比较有争议的是否合并起始状态等问题,只需修改重写的equals方法即可。
同样,对Transiton也采用同样的方法。
package stategraph;
import java.util.ArrayList;
public class AbstractTrans {
private String id;
private String name;
private AbstractState source;
private AbstractState target;
private String guard;
private ArrayList<String> triggers;
public AbstractTrans(String id, String name, AbstractState source,
AbstractState target, String guard,
ArrayList<String> triggers) {
...
}
同样,什么是相同状态,我们就可以在此定义,一旦规则有变,也只需修改这一处即可。(虽然最后限制了测试数据,这个地方没有了大用)
3、分离
在这里,类似于类图中的操作,把转移关系存储为图架构。需要注意的是,由于考虑了合并问题,因此可以通过一个类AbstractTrans来封装它,通过重写equals方法即可实现合并。
最后是最简单的顺序图:
顺序图总体比较简单。
1、还原
跟类图一样,按照层次关系:
UMLLifeline + UMLMessage ----> UMLInteraction
重组即可
public class AbstractInaction {
private String name;
private String id;
//UMLLifeline
private HashMap<String, UmlLifeline> idToLifeLine = new HashMap<>();
private HashSet<String> represent = new HashSet<>();
private HashMap<String, ArrayList<String>> nameToLifeLine = new HashMap<>();
//UMLMessage
private HashMap<String, UmlMessage> idToMessage = new HashMap<>();
//RELATION
private HashMap<String, Integer> messageIn = new HashMap<>();
...
}
2、关系与元素分离???
(1)Interation跟message是从属关系,不是同层次
(2)查询命令简单,无任何图算法相关操作
因此,这里不采用,只是加两个容器储存和统计一下即可
二、架构设计总结及OO方法理解的演进
四个单元对OO的架构设计是从面向过程到面向对象的一个巨大的转变。
第一单元,设计之初过多考虑功能性问题,导致多项式求导的设计非常没有层次感(把因子,项等杂糅在一起,通过递归处理,这样非常不OO)
第二单元,电梯的设计主要是多线程的安全性问题。我遇到了比较大的困难,不好的架构导致了线程不安全的可能性增加。但是经过参考多线程各种设计模式,并且把电梯调度分部分考虑,架构有了一定提升,但代码杂糅的程度还是比较高的。
第三单元,JML部分由于助教提供了一个大体框架,我也更多去思考设计的层面,整体的思路和结构都好转。
第四单元,UML是我考虑拓展性和架构设计优劣性最多的一次。更多注重下一次功能能否轻而易举增加的内容,并且在自己的debug和修改的过程中,体会到了非常大的好处。
我认为OO设计一开始不能一味考虑算法(功能性)和AC的问题,更多应该聚焦在层次拆解。
以第四次单元为例
(1)设计的最终目的(交互,最高层)
(2)分为哪几个大部分(顺序图,状态图,类图)
(3)对每个部分,有哪些小部分
主要考虑功能部分和元素部分
例如,状态图的元素有迁移、状态,功能是查询。
针对元素部分做适当有层次的封装(也就是充分展现类之间的继承关系,关联关系)
(4)采用什么样的数据结构??
(5)具体实现算法
其实,只要把(1)-(3)重点考虑(一般可以在纸上画出层次图(如使用UML))
(4)做一定考虑 (5)也就轻而易举了
综上所述
(1)思考和设计大于写码,好的层次一定程度上能让功能设计更加轻而易举。
(2)OO的设计是能帮助更好,更高质量的AC,如果一开始过分考虑AC,反而适得其反(惨痛教训)。
三、测试的理解
从最开始作业使用手动测试,到多项式的后两次作业开始尝试自己编写评测机制。
从固定结果的验证方法,到多线程不确定性如何验证正确性的测试编写。
从总体的黑箱测试到Junit做单元测试
从测试正确性到测试运行时间。
上述,大概就展现了我OO中的测试思路的转变(几乎每一次转变都是爆掉几个点之后的痛定思痛)
我认为测试
(1)正确性和运行时间兼顾(保证实现的正确性和使用方法和结构的优良性)
(2)总体测试和单元测试兼顾(我后来采用了边编码边JUNIT的历程,不得不说,正确性更有保障)
(3)自动评测非常重要(利用随机样例去测试,通过较长时间的运行,保证黑箱测试的覆盖率问题)
四、课程收获
2、抗压能力:多次临dll修改bug,有成功修改,有不成功修改的,这样的经历确实非常锻炼在短时间的修改代码并保证正确的能力。
3、需求分析能力:对指导书的内容进行分析,并且逐渐转化为程序语言。
4、还有对OO的理解和认可,对测试的体会,都是这次的巨大飞跃。
五、建议
1、建议课程组提供自主测试平台:本次测试和评测机测试存在运行时间上的误差,因此建议给大家开放一个平台,让所有人都可以在上面进行测试,得到运行时间,对多线程以及卡TLE的部分作业非常有帮助。
2、上机内容可以考虑提前发布一些预习任务,这样能让上机时候更加有针对性去应对,不至于手足无措。
3、适当预告后续作业的需求:有利于更好的拓展性设计。