BUAA_OO_博客作业四
BUAA_OO_博客作业四
1 第四单元两次作业的架构设计
1.1 第13次作业
类图
作业要求:通过实现UmlInteraction
这个官方提供的接口,来实现自己的UmlInteraction
解析器。
作业分析与架构设计:在阅读完接口源代码的架构之后,我发现需要注意的就是UmlElement
这个类,它属于一个顶层类,一共有9个孩子,分别是UmlAssociation
、UmlAssociationEnd
、UmlAttribute
、UmlClass
、UmlGeneralization
、UmlInterface
、UmlInterfaceRealization
、UmlOperation
、UmlParameter
,我们要搭建的结构就需要从这些类之间的关系出发考虑。通过StarUml软件生成的样例进行分析,我们可以得到一个树状结构,如下图所示。(根据函数要求搭建,省略了一些可能的包含关系)
其实,UmlClass
和UmlInterface
这两个平级类可以统一进行处理,虽然可能UmlInterface
相比较于UmlClass
少了一些内部属性,但是由于我们对于查询方法可以在自定义类内部进行区分查询。所以我构建了一个自定义类MyUmlInside
,包含的私有属性如下:
public class MyUmlInside {
private UmlClass umlClass;
private UmlInterface umlInterface;
private HashMap<String, UmlAttribute> attributes;
private ArrayList<MyUmlOperation> operations;
private ArrayList<UmlGeneralization> generalizations;
private ArrayList<UmlInterfaceRealization> interfaceRealizations;
private boolean isClass;
private HashMap<String, Integer> attributeName2num;
private HashMap<String, String> attributeName2id;
}
由于接口的继承关系是多继承承,而类的继承只能是单继承,所以我选择了把继承统一做成Hashmap
,但实际上如果是类,那么他只包含一项。这次的函数只有一个比较复杂,那个就是查找类的接口列表,由于我把类和接口存成同一个类型,所以我可以在这个内部类里面实现所有的查询,只需要在递归查询的时候根据当前项是类还是接口进行分别处理即可,而且由于这个设计,我也可以用这个函数进行查询接口的所有继承接口,出乎意料的方便了下一次作业的函数。(把这个内部函数展示出来)
public boolean isILL = false;
private List<String> iil = new ArrayList<>();
public List<String> implementInterfaceList(HashMap<String, MyUmlInside> id2myUmlInside) {
if (isILL) { //查看缓存
return iil;
}
List<String> fatherill = new ArrayList<>();
if (isClass) { //判断当前是类还是接口
if (generalizations.size() != 0) {
fatherill.addAll(id2myUmlInside.get(fatherId()).implementInterfaceList(id2myUmlInside));
}
for (UmlInterfaceRealization in : interfaceRealizations) {
iil.add(in.getTarget());
}
} else {
for (UmlGeneralization ge : generalizations) {
iil.add(ge.getTarget());
}
}
List<String> re = new ArrayList<>();
for (String id : iil) {
if (!fatherill.contains(id)) {
re.addAll(id2myUmlInside.get(id).implementInterfaceList(id2myUmlInside));
}
}
iil.addAll(re);
iil.addAll(fatherill);
isILL = true;
return iil;
}
由于UmlOperation
、UmlParameter
这两个类也有包含关系,所以我也构建了一个自定义类MyUmlOperation
用于存储对应关系。
public class MyUmlOperation {
private UmlOperation operation;
private ArrayList<UmlParameter> parameters;
private boolean isReturn; //读入参数的时候查询是否为返回参数
private boolean isPara; //读入参数的时候查询是否为普通参数
}
细心的人已经发现了,似乎UmlAssociation
和``UmlClass、
UmlInterface并不是平行关系,而应该是附属关系,但是为什么我要把这三个并列处理呢,原因是
UmlAssociation`虽然是属于一个类或者接口的附属属性,但是由于关联是双向的,也就是他不拘泥于当前的父亲类,所以我就将他当成一个单独的关系图进行读取处理,这样在外部进行双向计算地时候,不需要对其进行两次存储,避免了关联的另一个类还没有读入无法存储的尴尬。
由于输入的数据不一定按照mdj文件导出数据的树状结构进行输入,所以无法使用语法分析的方式进行处理,所以我们只能分批处理,先把上层的结构进行读入存储,第二遍扫描列表的时候把附属结构根据ParentId
进行添加,这样就可以避免了因为数据乱序导致的错误,所以实际地读取如下所示。
public void inputHandler(UmlElement... elements) {
for (int i = 0; i < elements.length; i++) { //第一次处理
UmlElement e = elements[i];
switch (e.getElementType()) {
}
}
for (int i = 0; i < elements.length; i++) { //第二次处理
UmlElement e = elements[i];
switch (e.getElementType()) {
}
}
}
当我们设计好结构,写好处理输入函数之后,后面对于每一个查询的函数的编写就可以在自定义类中进行复杂度较低的解决了。当然,这里特别说一句,对于需要进行递归查询的函数,最好做一个缓存,只要在顶层开始返回的时候填充缓存就可以实现,这样大大降低了查询时间。另外,再一次推荐一个hashmap
的内部函数,这个函数在我学会了之后,每一次的作业都能用到,而且大大减少了行数,使得更加可观。还有,最好每个自己搭建的类都重写toString()
方法,这样在进行debug的时候可以清晰的明白这个类究竟是哪一个(这个真的十分有用)。
HashMapName.merge(name, 1, (oldVal, newVal) -> oldVal + newVal);
1.2 第14次作业
类图
作业要求:在上次作业基础上,扩展解析器,使得能够支持对UML顺序图和UML状态图的解析,并能够支持几个基本规则的验证。
作业分析与架构设计:基本的架构可以沿用上一次作业,这一次主要增加了两个大的类,UmlStateMachine
和UmlInteraction
,所以我们只需要再上一次的梳妆台中增加树枝即可,如下图所示:
这次的输入处理思路和上次一样,那我就分别说明所构建的两个自定义类
顺序图
内部所含属性如下所示:
public class MyUmlInteraction {
private UmlInteraction interaction;
private HashMap<String, UmlMessage> id2messages;
private HashMap<String, UmlLifeline> id2lifelines;
private HashMap<String, Integer> lifeName2num;
private HashMap<String, String> lifeName2id;
private HashMap<String, Integer> lifeid2income;
private String name;
}
由于这次查对于顺序图都是O(1)的查询,所以每个函数基本上都是返回所属的size
,这里就不赘述。
状态图
内部所含属性如下所示:
public class MyUmlStateMachine {
private UmlStateMachine stateMachine;
private HashMap<String, UmlElement> id2states;
private HashMap<String, UmlState> name2states;
private HashMap<String, Integer> name2num;
//private HashMap<String, UmlFinalState> id2finalstates;
//private HashMap<String, UmlPseudostate> id2pseduostates;
private HashMap<String, HashSet<String>> transitions;
private int transitioncount = 0;
private String name;
}
这里我注释了两个HashMap
,这是由于作业要求的变更做出的改善。
在开始的说明中,pseduostate
和finalstate
的出现次数是不可知的,但是无论出现多少次,我们要视其为单独的两个状态,即初始状态和终止状态,所以我们要去做状态合并,而且在一开始的理解里,由于讨论区给出的说明是合理就要考虑,而我在starUml
中可以画出transition
到初始状态,transition
从终止状态出发的线,而且我还可以对于这些同样的点进行不同的命名,那么如果进行查询,这就需要在一开始读入的时候进行统一合并处理,所以那两个HashMap就产生了,当然后续又有其他的说明颠覆了我的理解,然后进行删改,这个行动重复了很多次,最终迎来了课程组的数据说明。
最后的说明如下:
于是我果断的删除了之前的合并操作和一系列复杂的结构,最终这个自定义类再一次归于平静。
对于模型有效性检查:
R002:不能有循环继承(UML008)
这个我们只要建立继承邻接表,然后通过Tarjan算法进行搜寻强连通图便可以找到循环继承的点。
R003:任何一个类或接口不能重复继承另外一个接口不能有循环继承
这个我直接利用了上一次作业的
implementInterfaceList()
函数,由于我这个既能查接口,也能查类,所以我只要比较返回的Arraylist的size大小和转换成Hashset的size大小一致性就可以判断是否有重复继承。
2 四个单元中架构设计及OO方法理解的演进
2.1 第一个单元
面向对象第一个单元是我正式接触JAVA面向对象层次的设计,一开始连对输入处理的正则表达式都是从零开始学起,可以说起步是步履蹒跚。当学会了如何进行输入处理后,我开始了架构设计,不同于之前的顺序编程思想,需要创建对于各个部分抽象的类,并且要实现类之间的消息传递,在这之中,我学到最多的就是对于层次化的设计,在编写程序之前,需要把这一次作业的所有部分进行分层,总结各个部分的联系,留出交互接口,这样我们就可以一个部分一个部分编写,每一个类的实现都可以注重自己本身的结构,而不是这个类什么都需要实现,这样大大提高了程序的可读性和规范性。第一个单元考察了对于多项式求导的问题,在设计好层次后,实际上还有一个重要的点就是继承和接口,由于多项式的底层类很多功能都是相似的,所以我们要在其中抽象出继承类或者接口来进行管理,这也是第一个单元训练的重点。
2.2 第二个单元
第二个单元是上这门课之前就有所了解的电梯,这个单元提出了多线程的概念,在之前用的C和C++我都没接触过线程,所以一开始进行了线程的理论学习,在基本掌握了线程实现方法后,开始了第一个电梯的搭建,由于第一个电梯不需要考虑调度问题,所以实际上我也只实现了两个线程的功能,一个输入处理线程,一个电梯线程,调度器线程只是一个简单的消息传递。到了后续的多电梯,我才意识到调度器线程的重要性,特别是线程之间通信的重要性,这其中,我掌握了原子性操作,学会了线程的wait
和notifyall
操作,知道了锁操作在线程之中的作用。电梯的整个设计架构就是基于三线程设计,输入处理线程-调度器线程-电梯线程,输入处理线程和调度器线程通过reqlist
进行请求管理,调度器线程和电梯线程通过通信和统筹规划分配任务,然后电梯内部对于任务队列进行电梯自身的调度操作,整个过程就相当于流水线,每一个步骤都需要仔细的规划。
2.3 第三个单元
第三个单元是对于JML的学习,这个单元开始设计到官方开源接口,我们需要做的就是对于接口的实现,理解接口所描述的JML语法,这个单元考察的不是自己的架构设计,而是对于一个任务接口的实现,当你有了基础的结构,你怎么样才能更好的完成。当然在这个单元也涉及到自己设计类,也就是对于地铁的图的查询,这个部分就是每个人施展手脚的舞台,我们在给予的任务说明后,每个人对于规范语言所描述的东西进行个人化实现,利用图的查询算法,无论是dijistra
还是prim
,对于不同要求的查询,怎么把一个函数中的不同地方抽象出来,使得这个函数根据传入的参数进行不同的查询,也是这个单元的重点,当我在写完查询图的算法之后,我可以说掌握了如何把函数更加模块化,把多种查询进行整体化设计。另外,我们也需要自己设计缓存图的存储结构,这个部分决定了最后的CPU
时间,如果在增加新的边的同时能够尽可能减少对于之前缓存查询的结果的影响,最后程序才能更加高效。
2.4 第四个单元
第几个单元是对于UML的学习,这个单元我们不仅要去学习官方开源接口的实现细节,还要去掌握UML图的画法和内容,类图、顺序图和状态图是这个单元的三大处理图,在学习的时候,我们需要自己在StarUML
软件里进行绘制学习,掌握画出来的图的结构层次和他所导出来的代码的关系,无论是父子结构还是关联关系,我们都需要进行存储管理。这个单元让我开始绘制读入的各个类的树状结构,构建自己的类去分割提供的类,把一个部分存贮到一个类中,这样在进行各个函数查询的时候可以在这个自己构建的类中进行内部操作,外部只要调用方法便能得到最终的结果。这个单元教会了我如何封装类,怎样才能更好的管理开源库所给的类,怎样设计才能高效的查找。
3自己在四个单元中测试理解与实践的演进
3.1 第一个单元
第一个单元的测试主要是对于正确性检验、边界条件的测试,对于正则表达式的检验,还有对于优化的检验。正确性检验可以通过PYTHON
之类的语言通过自己写的正则表达式进行大量数据的生成,然后进行正确性对比检验;边界条件的测试需要自行构造压力测试,包括超长的串和大数;正则表达式的检验这个部分也需要自己构造测试样例,然后还要进行理论论证;优化的检验这个部分就要看每个人的选择了,我在最后也只是实现基本的优化。
3.2 第二个单元
第二个单元多线程的测试是最难的测试,由于多线程运行的不可知性,很多错误都不能复线,所以构造大量的测试不是一个好的检验方法,在这个单元我主要时进行每个部分的理论论证,仔细的推敲每个部分的原子性,去除死锁的可能。
3.3 第三个单元
第三个单元给出了SMT Solver
或者JMLUnitNG
这两种自动测试方法,但是实际上的表现却不能令我满意,经常爆出不能理解的错误,所以我实际上还是进行的每个函数的单独测试,先保证每一个函数没有错误,然后进行自动生成测试数据的测试。然后就是和同学的对拍,大大降低了错误出现的可能性。
3.4 第四个单元
第四个单元的测试比较困难,我开始只是进行大概性质的覆盖测试,把可能想到的地方构建UML图然后生成测试数据,进行验证正确性,但是无法完全覆盖所有可能性。这里就要感谢同学们的互帮互助(特别是大白)构建了测试机,每个人把自己的JAR
包merge
上去,然后通过大量的UML图的测试数据生成,进行多人的对拍测试,很大程度降低了错误出现的概率。
4 自己的课程收获
面向对象设计与构造这门课首先让我很好的掌握了JAVA
这门语言和IDEA
这个编译器,在JAVA
的面向对象设计的过程中,我逐渐明白了抽象的实用性,从一开始多项式对于因子的抽象还处于对于作业的完成要求,到之后电梯开始设计一个真实世界拥有的东西,抽象可以很好的方便我们对于任务的管理,当然线程也是一个非常有用的东西,多线程的操作我想是以后必须的技能,虽然多线程的通信和协同确实需要我们仔细的设计,错误的不可复现和debug
的困难,但这都挡不住多线程的优势,可以大大提升程序的效率,更好的利用机能。第三个单元的JML标准化格式语言虽然锻炼了我们的严谨性,但是我总觉得这个JML似乎不是很完善。第四个单元还是很有乐趣性的,StarUML
软件的加入让我们去学习一个新的东西,加深了层次化设计的概念。
这门课不愧为北航计算机系的王牌课程之一,在一个学期的学习中锻炼的东西很多,我也可以说都是从零开始,一步一步学习编写测试,还有各种优化的思考和讨论,同学之间的理解碰撞,都在这门课中得到了很大的体现,这门课让我真正的利用JAVA
写出了有用的代码,让我理解到代码不是一次性的工作,你不仅要把当前的部分写好,还要流出充分的扩展性,自己要能为下一次可能的功能留出舒适的位置,这也是我之前一直没有的能力。
5 立足于自己的体会给课程提三个具体改进建议
1.针对于最后一个单元,我希望指导书可以对于要求更加明确,有些部分的歧义性可能在读者来看是无法找到正确答案的,最好能够在给出测试样例的时候覆盖到每一个测试函数,这样样例也可以作为我们读懂指导书的一个途径,第十四次作业所出现的讨论区问题让我对于架构进行了很多次改动,这每次推翻的理解让我每一次都要苦恼一番,希望标准能够早一些公布在指导书里。
2.我个人认为课上测试有些过于拘泥于形式,而且也没有给出答案,我们每一次也仅仅是写完就下课,并不能让我们学到什么,也无法改正课上测试的错误,希望能够让课上测试成为一个能够学习和改正错误的地方。
3.希望平时的理论课能够对于上一次作业的考察要点有一个说明,让我们明白课程想要考察的核心,这样让我们不仅仅是完成作业,还能够理解这个作业的核心,最好能说明一下课程组对于这个作业的思路和过程,这样可以起到醍醐灌顶的作用。