BUAA_OO_第四单元总结及课程总结
BUAA_OO_第四单元总结及课程总结
架构设计
本单元设计之前,我们要充分理解UML图,UML图的每个元素都有个独一无二的Id和parentId,很自然的就可以联想到树的结构,我们需要的就是维护许多棵树(森林)。
对于每个结点,也需要维护一个数据结构,因为给的官方包里是散装的,没有形成功能强大的结点和树形结构,所以我们需要自己重新写相应元素类。
第一类树(UML类图)
public class MyUmlClass{
private String ParentId ->UMLmodel
private ArrayList<UmlAttribute> // ParentId是UMLClass的属性,一个类拥有多个属性
private ArrayList<MyUmlInterface> //UmlClass实现的接口
private ArrayList<MyUmlOperation> //UmlClass的方法
private String fatherId ->MyUmlClass //如果有父类,则不为null,否则为null
//其余的Id,name等属性均与给的UmlClass相同
}
//可以把UmlInterface和UmlOperation均看做UmlClass的儿子。
public class MyUmlInterface{
private ArrayList<MyUmlInterface> //自己的父类接口,因为可以多继承,所以可以有多个private 父亲,若没有继承关系,则该list.size()==0
private String ParentId ->UMLmodel
//其余的Id,name等属性均与给的UmlInterface相同
}
public class MyUmlOperation{
private ArrayList<UmlParameter> //方法的参数,存储的UmlParameter的ParentId必须与private 自己的Id相同。
private String ParentId ->UmlClass
//其余的Id,name等属性均与给的UmlOperation相同
}
第二类树(UML顺序图)
public class MyUmlInteraction{
private HashMap<String, UmlLifeline> //用HashMap存的原因是通过ID可以O(1)查询到对private 应的UmlLifeline
private HashMap<String, UmlEndpoint> //同上理
private ArrayList<UmlMessage> //存的Message的ParentId一定跟当前类的Id相同
private String ParentId -> UmlCollaboration
//其余的Id,name等属性均与给的UmlInteraction相同
}
//UmlLifeline和UmlEndpoint,UmlMessage就是Interaction的儿子
第三类树(UML状态图)
public class MyUmlStateMachine{
private MyUmlRegion //一个状态机只有一个区域,数据已经规定,但剩下的元素其实都在UmlRegoin里,所以其实这只是个为了方便操作设置的吉祥物。
private String ParentId = null
//其余的Id,name等属性均与给的UmlStateMachine相同
}
public class MyUmlRegion {
private ArrayList<MyUmlState> umlStates;//存储区域里有所有的状态
private HashSet<String> terminal;//存放区域里所有的终点态,方便终止DFS
private MyUmlState initial;//区域的起始状态,方便开始DFS
String ParentId -> UmlStateMachine
//其余的Id,name等属性均与给的UmlRegion相同
}
public class MyUmlState {
private UmlState umlState; //中间状态,若不是,则为null
private UmlFinalState umlFinalState; //最终状态,若不是,则为null
private ArrayList<MyUmlState> toNext; //该状态可以直接迁移到的所有状态
private UmlPseudostate umlPseudostate; //起始状态,若不是,则为null
private HashMap<String, ArrayList<UmlEvent>> events;
//在这个类里我们需要用到三个构造函数,以分别对应普通状态,起始状态和最终状态
}
//这三者构成了一个垂直的树形结构
实现补充
我在整个大的UserApi中统计了全部的相关元素,对需要调用Id查询的均采用HashMap储存,对叶节点的元素则采用Arraylist存储。
比如
HashMap<String, MyUmlClass> umlClass//类图集合
HashMap<String, MyUmlInterface> umlInterfaces//接口集合
HashMap<String, MyUmlStateMachine> umlStateMachines//状态机集合
HashMap<String, MyUmlInteraction> umlInteractions//顺序图集合
HashMap<String, MyUmlRegion> umlRegions//区域集合
为了解耦合,基于我们的树实现,我们要实现的操作
UML图元素补充说明
- UMLAssociation:对应用户构造类之间的关联性(一般拥有两个end,对应两个类中的内容)
- UMLAssociationEnd:对应用户构造类互相关联性(0..1 表示可以有0个或者1个实例,表示对实例的数目没有限制,1 表示只能有一个实例,1.. 表示至少有一个实例)
- UMLGeneralization:对应用户构造类的泛化/继承
- UMLInterfaceRealization:对应用户构造接口的实现
类之间的六大关系:
-
泛化/继承(Generalization),Java单继承,UML可以多继承
-
接口实现(InterfaceRealization)
-
关联(Association)是一种拥有的关系,它使一个类知道另一个类的属性和方法
-
组合(Composition)是整体与部分的关系,但部分不能离开整体而单独存在
-
聚合(Aggregation)是整体与部分的关系,且部分可以离开整体而单独存在
-
依赖(Dependency)表示一个类A依赖于另外一个类B的定义
检查规则
- UML001:类图元素名字不能为空
- UML002:不能含有重名的成员。针对类图中的类(UmlClass),其成员属性(UMLAttribute)和关联对端所连接的UMLAssociationEnd不能有重名。
- UML008:不能有循环继承
- UML007:不能有重复继承
- UML011:接口所有属性均需要为public
- R006:Lifeline 必须表示在同一顺序图中定义的 Attribute(注意这里的属性的ParentId是UmlCollaboration,而Lifeline的ParentId是UmlInteraction)
- R007:一个 Lifeline 在被销毁后不能再收到任何消息
- UML033:所有 Final state 状态不能有任何迁出
- R009:一个状态不能有两个条件相同的迁出
各单元的架构、理解与感想
第一单元
第一单元作为这门课的第一次BOSS,最重要的时理解面向对象这一概念,脱离面向过程的思维,如何将一个组装好的整体自上而下层层剥离出一个个元素,再将一个个元素自下而上组装成一个整体,这是我们在这个单元需要掌握的思想。在解析表达式时,如果采用了模式匹配的方式,每次添加规则都需要重写正则表达式或匹配规则,很难利用上一次写出的规则。同时,对表达式的存储方式也是,每次都要做大量的改动。但如果采用词法与语法两级分析,存储时采用抽象语法树的方式,就能具有较好的扩展性。
归根结底,设计架构时应该充分考虑抽象,架构不可能脱离实现,但不应与实现有过强的耦合关系。
另一方面,第一单元中很重要的一点就是,抽象是有代价的。一般而言,抽象程度越高,架构的复杂度就越大。因此,需要在抽象程度与复杂度之间做一个权衡。
第二单元
第二单元是多线程的单元,这个单元我们着重学习的是对多线程的理解,当然为了性能分好必须要考虑好的调度策略,但这一般跟强测数据有关,不同数据下同一调度策略的表现性能差异会比较大。
对于多线程要如何实现正确的PV操作,如何避免轮询,是本单元的重中之重。
基于生产者消费者模式,本单元的线程安全问题主要是电梯线程和数据处理线程(调度器和输入线程)对乘客候乘队列的改写,在设计线程保护时,选择只在管理乘客表的类中进行加锁,而不在电梯线程或者调度器中加锁。乘客表类中,调用从乘客表取人进入电梯和从输入指令中将人放入乘客表这两个函数时,需要将乘客队列进行加锁。为了避免轮询,我们考虑到电梯和调度器本身是不容易唤醒的,所以当总等待队列和候乘队列没人时,我们将队列挂起,直到有人加入队列再唤醒队列。
要协调好wait和notify的使用。然后就是电梯的捎带策略和调度策略,由于多线程不好做测试,我们在写代码的时候一定要注重自己的逻辑严密性,最好先做个图,明白自己的电梯怎么载人和运输乘客的。本单元更多考察的就是细心和对多线程运行的理解。
第三单元
第三单元最重要的是形式化验证与单元测试。
JML编写和写代码还是有很多相似之处的,比如我们熟悉的循环操作\forall、存在操作\exists等。规格化语言消除了自然语言的歧义,提供了一个统一的规范,有利于检测代码的正确性。但是,我们不能直接把简单的JML语言理解为真正的代码实现,因为真正的代码实现需要权衡算法、数据结构等。一定要认识到规定前因后果和实现过程是不同的,比如一定要考虑时间复杂度,\(O(n^2)\)以上的复杂度达咩。另外,中测等于没测,不能信。在规格化测试的基础上,需要我们手动构造极端数据,并且利用测评机加以测试,才能更好地验证代码的正确性。
第四单元
第四单元是帮助我们更好地理解,学习UML图,掌握工业的规范,对我们以后工业级编程有着很深的帮助。
这单元的实现主要是用树的思想去思考,如果我们能自下而上地去实现,那么就没有难题。
测试理解与实践演进
在OO课程开始之前,计组课上的测试经历,已经充分理解了“与标准答案对比”和“与其它实现对比”两种测试方案,而OO中测试理论上最大的收获,就是单元测试与形式化验证。之前虽然听说过单元测试,但实际上并没有实践过。
在测试的实践上,我充分认识到了极端数据的重要性和小细节思考的重要性。因为被其他人hack,第二单元好几次都是问题出在极端数据,第一单元都是在小细节上。另外,构造复杂数据也很重要。即使对每一种基本情况都可以正确处理,在多种情况复合时可能就会产生状态不一致等问题导致错误。
课程收获
熟练使用了java语言
之前虽然能写java程序,但并不熟练,经常会因为语法问题查资料。经过OO课程之后,语法自不必说,还掌握了多个容器类以及多线程的使用,JML规格和UML图。
面向对象思想
面对一个复杂的问题,如何将其抽象,设计架构,并做出高效且正确的实现。
测试能力
手动构造评测机,白盒测试,对拍以及形式化验证等。
课程改进建议
- 为了让指导书的实现重点和细节更突出,可以将重点部分与其他部分分隔开。并发放PDF版本的指导书,这样可以用颜色区分文字,让同学们更好地理解指导书。
- 建议实验课可以公布结果,不然无法确定正确性,感觉实验课的启迪作用会减小。
- 预习的pre感觉是一次额外的架构实现作业,其实可以像实验那样弄成练习,更多的是让同学们学习使用java,java的语言特性,正则表达式,多种容器等等。