一、总结本单元两次作业的架构设计
(1)第一次作业
第一次作业基于不同element
在UML规格中的从属关系来设计架构。在构造顶层MyUmlInteraction
类时,需要对加入进来的每一个element
进行分析,判断它的ElementType
并根据parentId
以及其他性质决定它的存储位置。
对于UmlClass
,UmlOperation
等,建立了MyClass
、MyOperation
类,用于存储element
自身的基本信息以及在后续需要用到的其他属性,并用HashMap
存储id、name与具体的类之间的映射关系。如:根据UmlClass
与UmlOperation
之间的从属关系,MyClass
类中也需要存储所有对应的MyOperation
类;根据继承与关联关系,MyClass
类与MyInterface
类中也有存有对应的父类信息以及关联类信息。
对于如UmlParameter
、UmlAttribute等,
通过getparentid()可以找到其父类,由此可以将它们直接存储在对应的父类类中,如UmlAttribute储存在
对应的MyClass
中。
对于UmlInterfaceRealization
、UmlAssociation
、UmlGeneralization
,它们表示的是class或interface的某种关系,我将它们所包含的关系提取出来,储存在对应的class或interface中。
对于一般方法的实现,先找到classname
对应的class
,再调用MyClass
类中对应方法,返回结果或产生异常报告错误。
对于与父类有关方法的实现,先调用classname
对应的class
自身的方法,再调用parentClass
的对应方法,在访问父类的父类(如果有的话);如此递归调用,逐级返回信息,最后得到结果并输出。
(2)第二次作业
第二次作业在第一次作业的基础上,增加了对于顺序图以及状态图的处理,需要分析更多种类的UmlElement
。
本次作业有关类图的操作可以直接继承第一次作业完成的部分;将interaction、lifeline、message归于顺序图,将state、pseudostate、finalstate、transition、region、statemachine归于状态图,其余全归于类图;在主要类中new出对应的图,将归好类的元素传入对应的图中进行构建和处理。Precheck的类图检查部分,引用并检查的就是在主要类中构建的类图。我新建立了UmlRegion和UmlStatemachine,对于类图、状态图、顺序图都有各自的
Interaction,对于类图的规则检查有therules
。
本次作业,对于类图的规则检查是一个比较难理解的点,我具体的实现方式如下:
① R001:针对下面给定的模型元素容器,不能含有重名的成员(UML002)
public void checkForUml002() throws UmlRule002Exception {
makelist();
HashSet<AttributeClassInformation> list = new HashSet<>();
for (MyUmlClass umlclass : classList1.values()) {
list.addAll(umlclass.checkForUml002());
}
if (!list.isEmpty()) {
throw new UmlRule002Exception(list);
}
}
public HashSet<AttributeClassInformation> checkForUml002() {
makelist();
HashSet<AttributeClassInformation> result = new HashSet<>();
HashSet<String> differNames = new HashSet<>();
for (String endname : differAssociationEnameList.keySet()) {
if (endname == null) { continue; }
if (differAssociationEnameList.get(endname) != 1) {
differNames.add(endname); } }
for (String attriname : differAttriNames.keySet()) {
if (attriname == null) { continue; }
if (differAttriNames.get(attriname) != 1) {
differNames.add(attriname); } }
for (String endname : differAssociationEnameList.keySet()) {
if (endname == null) { continue; }
if (differAttriNames.containsKey(endname)
&& !differNames.contains(endname)) {
differNames.add(endname); } }
Iterator it = differNames.iterator();
while (it.hasNext()) {
String name = (String)it.next();
result.add(new AttributeClassInformation(name, getName())); }
makelist();
return result;
}
② R002:不能有循环继承(UML008)
public void checkForUml008() throws UmlRule008Exception {
makelist();
HashSet<UmlClassOrInterface> list = new HashSet<>();
HashMap<String, MyUmlClass> classList5 = new HashMap<>();
classList5.putAll(classList1);
for (MyUmlClass class1 : classList5.values()) {
if (list.contains((UmlClassOrInterface)
(classList4.get(class1.getid())))) {
continue;
}
if (class1.getHaveFather() == false) {
continue;
}
String firstClassid = class1.getid();
HashSet<UmlClassOrInterface> list2 = new HashSet<>();
list2.add((UmlClassOrInterface)(classList4.get(class1.getid())));
HashSet<String> list3 = new HashSet<>();
list3.add(class1.getid());
while (class1.getHaveFather()) {
if (list3.contains(class1.getFatherid())) {
if (class1.getFatherid().equals(firstClassid)) {
list.addAll(list2);
}
break;
} else {
class1 = classList1.get(class1.getFatherid());
list2.add((UmlClassOrInterface)
(classList4.get(class1.getid())));
list3.add(class1.getid());
}
}
}
for (UmlInterface umlInterface : interfaceList3.values()) {
if (list.contains((UmlClassOrInterface)umlInterface)) {
continue;
}
if (!interfaceList2.containsKey(umlInterface.getId())) {
continue;
}
HashSet<String> path = new HashSet<>();
path.add(umlInterface.getId());
if (searching(umlInterface.getId(),
umlInterface.getId(), path) == true) {
list.add((UmlClassOrInterface)
(interfaceList3.get(umlInterface.getId())));
}
}
if (!list.isEmpty()) {
throw new UmlRule008Exception(list);
}
}
public boolean searching(String fromid, String id, HashSet<String> path) {
makelist();
if (interfaceList2.containsKey(id)) {
ArrayList<String> nextInterfaceList =
interfaceList2.get(id);
for (int i = 0; i < nextInterfaceList.size(); i++) {
String nextid = nextInterfaceList.get(i);
if (nextid.equals(fromid)) {
return true;
}
if (path.contains(nextid)) {
continue;
}
HashSet<String> newpath = new HashSet<>();
newpath.addAll(path);
newpath.add(nextid);
if (searching(fromid, nextid, newpath) == true) {
return true;
}
}
}
return false;
}
③ R003:任何一个类或接口不能重复继承另外一个接口(UML009)
public void checkForUml009() throws UmlRule009Exception {
makelist();
HashSet<UmlClassOrInterface> list = new HashSet<>();
for (String interfaceid : interfaceList1.keySet()) {
if (!interfaceList2.containsKey(interfaceid)) {
continue;
} else {
List<String> result1 = new ArrayList<>();
result1.add(interfaceid);
for (int i = 0; i < result1.size(); i++) {
if (interfaceList2.containsKey(result1.get(i))) {
result1.addAll(interfaceList2.get(result1.get(i))); } }
HashSet<String> result2 = new HashSet<>();
for (int i = 0; i < result1.size(); i++) {
if (interfaceList1.containsKey(result1.get(i))) {
if (!result2.contains(result1.get(i))) {
result2.add(result1.get(i));
} else {
list.add((UmlClassOrInterface)
(interfaceList3.get(interfaceid)));
break; } }
}
}
}
for (MyUmlClass class1 : classList1.values()) {
List<String> result1 = class1.getInterfaceList();
if (class1.getHaveFather()) {
MyUmlClass class11 = class1;
String fatherId = class11.getFatherid();
while (true) {
class11 = classList1.get(fatherId);
result1.addAll(class11.getInterfaceList());
if (!class11.getHaveFather()) {
break; }
fatherId = class11.getFatherid();
}
}
for (int i = 0; i < result1.size(); i++) {
if (interfaceList2.containsKey(result1.get(i))) {
result1.addAll(interfaceList2.get(result1.get(i)));
}
}
HashSet<String> result2 = new HashSet<>();
for (int i = 0; i < result1.size(); i++) {
if (interfaceList1.containsKey(result1.get(i))) {
if (!result2.contains(result1.get(i))) {
result2.add(result1.get(i));
} else {
list.add((UmlClassOrInterface)
(classList4.get(class1.getid())));
break;
}
}
}
}
makelist();
if (!list.isEmpty()) {
throw new UmlRule009Exception(list);
}
}
本次作业中存在的问题,是在进行第二项规则检查时,没有考虑到两个子类同时继承一个父类的情况,使得程序在一些情况下会出现死循环。
二、总结自己在四个单元中架构设计及OO方法理解的演进
第一单元:
在这一单元的学习过程中,我积极思考、认真完成,同时我及时与同学交流想法、共同研究、分享思路,这让我在写代码的过程之中少了很多不必要的麻烦,在遇到了一些挑战和困难的同时,也收获良多。这三次作业使我对面向对象有了基本的理解,也在一定程度上区分了面向对象和面向过程,在编程思路上也有所转变。
第一次作业:
用面向对象的形式、以面向过程的思想来写的代码,所以复用性很差。设置了两个类,一个类中进行输入输出,另一个类中包含所有处理过程。
所谓万事开头难,第一次作业就让我一头雾水、不知从何入手。这是我第一次接触面向对象的思想,并没有弄清楚面向对象和面向过程的区别是什么,这让我很苦恼。由于时间紧任务重,在没有很好理解面向对象的基础上,我便匆匆忙忙开始写我的第一次作业。可以说,第一次的作业我还是按照C语言的思路来完成的,大体上就是通过划分过程从而写出若干个子函数,最终实现功能。虽然作业通过了强测所有的测试点,但是代码复用性并不好,题目一旦增加其他需求,便需要重新进行构建。总的来说,第一次的作业带给了我很多思路,也让我开始去理解面向对象的思想,虽然有不完善的地方,但我个人还是挺满意的。
第二次作业:
在第一次的基础上,重新进行了架构,设置了一个Poly类,这个类中抽象出系数、x的指数、sin(x)的指数、cos(x)的指数这四个对象,并利用ArrayList构建动态数组,在Dispose(字符串处理类)中进行调用和处理,最终通过toString得到输出的字符串,并在Test类中进行输入和输出。
相比于第一次作业,第二次作业增加了三角函数的求导和连乘。由于第一次作业架构不是很合理,我不得不重新进行构建。不过这一次我思路开始转变,类的划分也比上一次清晰很多,这让我完成地相对顺利。唯一遗憾的是自己在优化时没有考虑到所有可能出现的情况,导致程序出现了明显的bug。
第三次作业:
我共设置了8个类,分别是Test类(负责输入输出)、Dispose类(负责字符串合法性判断)、Addorsub类(负责对加减的形式进行处理)、Multi类(负责对乘的形式进行处理)、Nest类(负责对嵌套的形式进行处理)、Direct类(负责对可以直接处理的形式进行处理,即最基本情况的处理)、Method类(负责判断用哪种方式进行处理,即Addorsub、Multi、Nest、Direct中的一种)、Dofunc类(一个功能接口)。通过递归思想和树的构建来一层一层获得求导的情况,直至全部到达最基本形式,然后一层层返回,拼凑成最终形式。
相比于前两次求导的作业,第三次作业的难度又提升了一个等级,看完指导书后完全没有思路,因此我与几个同学决定大家一起共同理解题意,试图寻找思路。同时,我也询问了任课老师,老师也给了我们很多指点,这让我们的思路逐渐清晰。这次作业我们运用了接口来实现功能,并运用递归的思想完成求导的任务,最终按时完成了任务。只是让我很懊恼的是,我再次犯了很低级的错误,处理特殊情况时不小心打错了,导致程序出现了非常低级的bug,还是自测过程中不够细致造成的。
第二单元:
第一次电梯作业(多线程、单电梯、非捎带):在第一次作业中我一共设置了五个类:Test类包含主函数,负责实例化进程,并将共享类作为参数传入两个线程;Poly类存储请求,每个请求对应一个Poly;Request类即为输入请求类,将请求不断读入共享队列;Share及共享类,其中包含了请求总队列;Elevator及电梯队列,不断从共享队列中读出请求,并执行。在这次作业中,我采用傻瓜调度的方式,及不考虑时间性能方面,Request每次只能向共享队列中输入一条请求,Elevator只能从共享类读出一条请求,采取先入先出的原则,及先发出的请求先被执行。于此同时,Request和Elevator并行,协同进行。
第二次电梯作业(多线程、单电梯、捎带):在第二次作业中我一共设置了五个类:Test类包含主函数,负责实例化进程,并将共享类作为参数传入两个线程;Poly类存储请求,每个请求对应一个Poly;Request类即为输入请求类,将请求不断输入共享队列;Dispatch及共享类,其中包含了请求总队列,以及一个电梯一轮捎带所执行的请求队列,还包括了电梯一轮捎带的一些基本信息。除此之外,这个类还是调度器,调度器负责判断捎带的条件,将可以被捎带的请求放入捎带请求队列;Elevator及电梯队列,执行可稍带队列中的指令,在经过每一层时判断在这一层有无进出操作,并对队列进行更新。在这次作业中,我讨论了6种捎带模式,其他与第一次相同。于此同时,Request和Elevator并行,协同进行。
第三次电梯作业(多线程、多电梯、捎带):在第三次作业中我一共设置了五个类:Test类包含主函数,负责实例化进程,并将共享类作为参数传入InputRequest线程和三个Elevator线程;Poly类存储请求,每个请求对应一个Poly;InputRequest类即为输入请求类,将请求不断输入共享区,及Dispatch;Dispatch及共享类,其中包含了A、B、C三个电梯的三个请求总队列,并起到调度器的作用,及判断从InputRequest中输入的请求应该放入哪一个电梯的队列,如果遇到不能直达的情况,则将指令进行拆分,两个分指令分别放入不同的电梯队列。;Elevator及电梯队列,包含一个电梯一轮捎带所执行的请求队列,还包括了电梯一轮捎带的一些基本信息。除此之外,这个类还包括判断捎带的条件,将可以被捎带的请求放入捎带请求队列,电梯执行可稍带队列中的指令,在经过每一层时判断在这一层有无进出操作,并对队列进行更新。在这次作业中,我讨论了6种捎带模式,其他与第二次相同。于此同时,Request和三个Elevator并行,协同进行。
第三单元:
关于第9次作业,在主类Main中,通过将MyPath和MyPathContainer传给AppRunner,实现对这两个功能模块的测试。MyPathContainer类存在对Path类型量的各种操作,因此一定程度上依赖于Path类。
在MyPath类中,用Arraylist和hashmap同时存储一条路径中的所有点。当需要判断是否包含某个点时,用hashmap;当所进行的操作需要对这个路径中所有点按输入时的顺序进行遍历,或者取出按顺序的第i个点,或者对不同的路径进行相同比较、字典序比较时,使用arraylist。同时,因为在MyPathContainer中存在以MyPath类型的变量作hashmap的key值得情况,所以需要重写MyPath类中的equals和hashcode。
在MyPathContainer类中,用2个HashMap分别存储path到pathid(paths),和pathid到path(pathids)的对应关系。还需要一个hashmap(名为distinct)存储各个path中不同的点。名为paths的hashmap,key值类型为path,表示路径,value值类型为Integer,表示对应的路径编号;名为pathids的hashmap,key值类型为Integer路径编号,value值类型为path,表示对应的路径;名为distinct的hashmap,key值类型为Integer,表示节点序号,value值类型为Integer,表示对应节点的出现次数。
我的第10次的oo作业,是在第9次作业的基础上,在第9次作业的基础上增加了MyGraph类的功能而完成的。在这些增加的功能中,比较特殊的是:isConnect()判断fromNodeId的对应点和toNodeId的对应点是否连接,以及getShortestPathLength在相连的情况下求从fromNodeId的对应点到toNodeId的对应点的最短距离。
这对于这两个功能,我的思路主要是:
在每次addpath时,除了要更新表示path 和pathid对应关系的两个hashmap,存储不同节点的distinct结构;还要更新由所有path构成的无向图,清空以前的“是否连通”“最短距离”储存结果。
在每次removepath时,同样,在remove pathids paths distinct这3个hashmap的同时,还要删除无向图中的这个path,同时清空以前的“是否连通”“最短距离”储存结果。
在“判断是否相连”“求最短距离时”,首先查找以前的“是否连通”“最短距离”储存结果,如果有,直接返回,如果没有,针对无向图graph使用广度遍历算法,并且保存所有中间结果和最终结果。最后,总能判断出“是否连通”或“最短距离”。
在isConnected和getShortestLength函数中,我的思路是这样的:
现判断fromNodeId的对应点和toNodeId的对应点是否存在于现有无向图中,如果否,那么抛出异常;如果都存在于现有无向图,那么判断这两点是否是同一点,是否在lengthgraph中已经保存了它们之间的连接关系或最短距离。在这里,如果对应的fromNodeId和toNodeId存在于lengthgraph中,如果对应数字-1,则两点不相连,否则对应数字就是两点之间的距离。
对应关系无法在lengthgraph中取得,那么,需要进行一次fromNodeId的对应点到其他能直接或间接相连的所有点的广度优先遍历。我的广度优先遍历,是在《数据结构》课程的广度优先遍历,和CDSN上类似代码分享的基础上完成的,在不停地调用自身中,需要这些参数:
其中,queue1的key与list1的内容相同,表示当前“层”中需要被查找的点,queue2中的点是已经被查找过的点。每次调用这个广度优先遍历函数之前,都需要重新构建符合当前情况的queue1、list1和queue2。
同时,我对我在MyGraph类中2个双重嵌套Hashmap进行拆分的结果。原本,我在MyGraph类中使用了这样的2个Hashmap:
来表示若干条路径组成的无向图,以及储存每次进行广度遍历的中间结果和最终结果。后来在写代码的过程中,我发现,如果这样设计,每次在graph和lengthgraph中进行查找时,都要先通过“.get()”取出整个HashMap<Integer,Integer>,再进行查找;每次对graph和lengthgraph中的内容进行增减时都要先通过“.get()”取出整个HashMap<Integer,Integer>,进行增减,再放回原位。我认为这种方式太过笨拙,占用空间也相对大了很多,因此将嵌套在内层的HashMap<Integer,Integer>取出,封装成Line类,并构造相应的一些函数完成“查找”、“更改”等功能。进行封装后,我的MyGraph类更改为:
我的第11次的oo作业,是在第10次作业的基础上,在第10次作业的基础上增加了MyRailwaySystem类的功能而完成的。在这些增加的功能中,比较特殊的是:getConnectBlockCount()判断连通块的数目,isConnect()判断fromNodeId的对应点和toNodeId的对应点是否连接,getShortestPathLength在相连的情况下求从fromNodeId的对应点到toNodeId的对应点的最短距离,getLeastTicketPrice在相连的情况下求从fromNodeId的对应点到toNodeId的对应点的最低票价,getLeastTransPrice在相连的情况下求从fromNodeId的对应点到toNodeId的对应点的最少换乘,getLeastUnpleasentValue在相连的情况下求从fromNodeId的对应点到toNodeId的对应点的最小不满意度。
相对于第10次作业,我这次作业主要进行了这些设计:
我用Map类来存储彼此不联通的子图。每次新加入一个path,我都首先判断,这个path是需要加入一个已经存在的子图(也就是说,这个path和这个已存在的子图是相连的,有相同的点),还是需要以这个path为基础new一个新的子图。
如果是加入已有子图中(记为子图A),会不会有不同于子图A的子图B,因这个path而联通在了一起。如果有这样的子图B,那么,需要销毁子图B,将子图B中所有path和对应的pathid,一组一组地加入子图A中。
每次remove一个path,也都需要销毁这个path所在的子图(记作子图A),再将子图A中不同于path的其他路径,进行“加入操作”;又因为这些“不同于path的其他路径”已经有对应的pathid,因此不能直接采用addpath操作,而应该使用相似而不同的insertpath函数。当需要用getConnectBlockCount()判断连通块的数目时,直接返回子图的数量即可。
在计算最低票价、最少换乘、最小不满意度时,都涉及到不同path同一站点的换乘问题。我设计了区别于普通node的NodePathId,用于配合拆点法使用(虽然事实证明拆点法易燃易爆炸)。只有两个NodePathId的点序号node和所在路径序号pathId都相同时,才判断这两点是同一个点。因为存在用NodePathId作hashmap的key的情况,需要重写其equals函数和hashcode函数。
在计算最短距离、最低票价、最少换乘、最小不满意度时,我使用的都是迪杰斯特拉算法。但由于在计算最低票价、最少换乘、最小不满意度时,需要考虑不同线路的换乘,需要采用拆点法,因此,我完成了两个迪杰斯特拉算法函数,分别适用于“普通node + 最短距离”与“NodePathId + 最低票价、最少换乘、最小不满意度”。
在计算最低票价时,我首先看以往的迪杰斯特拉有没有顺带着得出并保存我想要的结果(正着找一次,反着找一次);如果没有找到,那么,需要进行一次从fromNodeId的对应点到剩下同一连通图中所有点的迪杰斯特拉查找,在此之前,因为我的计算最短距离、最低票价、最少换乘、最小不满意度使用同一个迪杰斯特拉算法,所以我们需要将迪杰斯特拉需要用到的一些参数(在计算最短距离、最低票价、最少换乘、最小不满意度时有所不同的部分)提前算好,传入迪杰斯特拉函数中。
计算最少换乘、最低不满意度的算法与此大同小异(其实我是复制自己的计算最低票价的函数写的最少换乘、最低不满意度来着)。
在向Map中加入一个path时,我需要做的事情包括:
更新用于表示path和pathid对应关系的hashmap,在用于计算最短距离、最低票价、最少换乘、最小不满意度的无向图中讨论是否插入新的“行”(抽象地理解为一个二维矩阵,或者链表);为方便拆点法的使用,构建不同path上相同node的关系(注意,在不同无向图中赋值不同);将新加入的path,一条边一条边地加入;最后,更新“某一点都在哪些path中出现”的hashmap,清空之前所有保存的迪杰斯特拉算法的结果。
第四单元:
第一次作业基于不同element
在UML规格中的从属关系来设计架构。在构造顶层MyUmlInteraction
类时,需要对加入进来的每一个element
进行分析,判断它的ElementType
并根据parentId
以及其他性质决定它的存储位置。
对于UmlClass
,UmlOperation
等,建立了MyClass
、MyOperation
类,用于存储element
自身的基本信息以及在后续需要用到的其他属性,并用HashMap
存储id、name与具体的类之间的映射关系。如:根据UmlClass
与UmlOperation
之间的从属关系,MyClass
类中也需要存储所有对应的MyOperation
类;根据继承与关联关系,MyClass
类与MyInterface
类中也有存有对应的父类信息以及关联类信息。
对于如UmlParameter
、UmlAttribute等,
通过getparentid()可以找到其父类,由此可以将它们直接存储在对应的父类类中,如UmlAttribute储存在
对应的MyClass
中。
对于UmlInterfaceRealization
、UmlAssociation
、UmlGeneralization
,它们表示的是class或interface的某种关系,我将它们所包含的关系提取出来,储存在对应的class或interface中。
对于一般方法的实现,先找到classname
对应的class
,再调用MyClass
类中对应方法,返回结果或产生异常报告错误。
对于与父类有关方法的实现,先调用classname
对应的class
自身的方法,再调用parentClass
的对应方法,在访问父类的父类(如果有的话);如此递归调用,逐级返回信息,最后得到结果并输出。
第二次作业在第一次作业的基础上,增加了对于顺序图以及状态图的处理,需要分析更多种类的UmlElement
。
本次作业有关类图的操作可以直接继承第一次作业完成的部分;将interaction、lifeline、message归于顺序图,将state、pseudostate、finalstate、transition、region、statemachine归于状态图,其余全归于类图;在主要类中new出对应的图,将归好类的元素传入对应的图中进行构建和处理。Precheck的类图检查部分,引用并检查的就是在主要类中构建的类图。我新建立了UmlRegion和UmlStatemachine,对于类图、状态图、顺序图都有各自的
Interaction,对于类图的规则检查有therules
。
三、总结自己在四个单元中测试理解与实践的演进
第一单元,在进行测试时:第一次作业我通过了所有强测的测试点,在互测中也没有被hack,我也没有找到自己程序的bug;第二次作业中,细致来说,程序有两个bug。第一个是在判断字符串合法性时,我忘记去掉字符串前后的空格了,导致合法的情况被误判为非法形式了。第二个是在对输出的字符串进行优化时,我没有考虑清楚所有可能出现的情况,将所有的“+1*”全部替换成了“+”、将所有的“-1*”全部替换成了“-”,这就导致当求出的式子中存在指数未-1且后面与其他因子相乘时被误删掉,导致程序出现了bug;在第三次作业中我犯了一个很低级的错误,在x的幂函数求导时,为了输出能简洁一些,我将x^1单独考虑了,结果手误打错了,本想打“1”,结果打成了“x“,结果导致输入x^1时,导数输出x而不是1。
这三次作业我都在一定程度上进行了新的构建:第一次是用面向对象的形式、以面向过程的思想来写的代码,所以复用性很差。设置了两个类,一个类中进行输入输出,另一个类中包含所有处理过程;第二次作业,我在第一次的基础上,重新进行了架构,设置了一个Poly类,这个类中抽象出系数、x的指数、sin(x)的指数、cos(x)的指数这四个对象,并利用ArrayList构建动态数组,在Dispose(字符串处理类)中进行调用和处理,最终通过toString得到输出的字符串,并在Test类中进行输入和输出;第三次作业,我共设置了8个类,分别是Test类(负责输入输出)、Dispose类(负责字符串合法性判断)、Addorsub类(负责对加减的形式进行处理)、Multi类(负责对乘的形式进行处理)、Nest类(负责对嵌套的形式进行处理)、Direct类(负责对可以直接处理的形式进行处理,即最基本情况的处理)、Method类(负责判断用哪种方式进行处理,即Addorsub、Multi、Nest、Direct中的一种)、Dofunc类(一个功能接口)。通过递归思想和树的构建来一层一层获得求导的情况,直至全部到达最基本形式,然后一层层返回,拼凑成最终形式。
第二单元,在进行测试时,我分析了自己未通过的公测用例和被互测发现的bug;特征、问题所在的类和方法;特别注意分析哪些问题与线程安全相关,比如在自测的过程中出现过线程错乱的问题,因为没有锁起而造成的;关联分析bug位置与设计结构之间的相关性,我在在提交的过程中有过CPU超时的现象,说明程序中存在轮询的情况,及CPU一直处于等待状态,解决的办法是在continue前面加上一个sleep或者使用wait和notify。
第一次电梯作业是多线程、单电梯、非捎带,我主要测试了连续输入和程序停止部分,因为此次作业仅有一台,且基本上都是傻瓜调度,因此没有查到错误;第二次电梯作业是多线程、单电梯、捎带,我使用了第一次电梯作业中的强测数据对别人程序进行了测试。除此之外,在写程序时,我积攒了一些测试样例,在互测时起到了作用。第二次作业相比第一次增加了捎带功能,因此,我主要针对各种捎带的情况进行了测试。我在看他人代码的时候弄清楚他的捎带策略,根据其结构设计一些设计样例来测试其性能。我发现大家的程序运行结果还是有很大的差别,同一组数据有的程序30s之内就能运行完,有的却要70s左右,但是测试结果表明,它们都在合理时间范围内,只是性能上有所差距。第三次电梯作业是多线程、多电梯、捎带,我先在互测时用第二次电梯作强测的数据进行了一下测试,除此之外,在写程序时,我积攒了一些测试样例,在互测时起到了作用。相比前两次作业,第三次作业增加了两个电梯,同时存在不直达需要转乘的情况,因此我将全部需要转乘的情况全部列了出来,依次进行测试,判断其是否存在转乘错误的情况。我在写测试数据时会先看明白它们的捎带规则和转乘规则,然后根据不同的架构和设计方式编写测试用例。结果发现,大家的转乘方式以及捎带规则还是有很大的差别,性能上也有一定的差距。测试数据如:1-FROM--3-TO-2,2-FROM-10-TO-19
线程安全和设计原则是我在做这一单元作业时思考做多的问题。好的设计架构是保证线程安全和可复用性的保证。线程安全是多线程编程时的计算机程序代码中的一个概念。在拥有共享数据的多条线程并行执行的程序中,线程安全的代码会通过同步机制保证各个线程都可以正常且正确的执行,不会出现数据污染等意外情况。多个线程访问同一个对象时,如果不用考虑这些线程在运行时环境下的调度和交替执行,也不需要进行额外的同步,或者在调用方进行任何其他操作,调用这个对象的行为都可以获得正确的结果,那么这个对象就是线程安全的。类要成为线程安全的,首先必须在单线程环境中有正确的行为。如果一个类实现正确,那么没有一种对这个类的对象的操作序列可以让对象处于无效状态,观察到对象处于无效状态、或者违反类的任何不可变量、前置条件或者后置条件的情况。此外,一个类要成为线程安全的,在被多个线程访问时,不管运行时环境执行这些线程有什么样的时序安排或者交错,它必须仍然有如上所述的正确行为,并且在调用的代码中没有任何额外的同步。其效果就是,在所有线程看来,对于线程安全对象的操作是以固定的、全局一致的顺序发生的。
我在这三次作业中都设了一个input线程,及不断地向队列中输入数据,一个elevator线程(第三次作业对应三个elevator线程),及从共享队列中读出数据后指导电梯运行,还有一个共享类,这个共享类中存储了总队列,并起到调度器的作用。从线程安全的角度考虑,当共享类中的数据被读取或存入时,同时只能有一个进程对这个数据进行操作且进行操作的代码的执行不能被打断,这样就保证了程序在运行的过程中不会因为进程混杂交错而造成数据错误或者运行错误。为了保证线程安全,我将方法锁了起来,及使用synchronized 关键字,代表将这个方法加锁,相当于不管哪一个线程(例如线程A),运行到这个方法时,都要检查有没有其它线程B(或者C、 D等)正在用这个方法(或者该类的其他同步方法),有的话要等正在使用synchronized方法的线程B(或者C 、D)运行完这个方法后再运行此线程A,没有的话,锁定调用者,然后直接运行。它包括两种用法:synchronized 方法和 synchronized 块。
关于第三单元,第9次作业,在MyPath类中,只是用hashmap,无法很好地按存储的顺序取出路径中的所有点,同时在取得过程中可能发现各种错误,所以出现了各种tle问题。在改用arraylist和hashmap的结合体后,可以解决所有出现的tle问题。第11次作业,在计算最少换乘等问题时,使用了拆点法+迪杰斯特拉,但由于拆出来的点实在过多,因此,在强测中出现了一些tle的点。改用“普通点(二维数组储存)+弗洛伊德”的方法,可以解决所有出现的tle问题。
经过这个单元对JML规格语言的学习,我已经在大体上掌握了这种规格的使用方法。在我看来,这种语言主要是与“面向对象”的思想相结合,表示“希望下一步能得到怎样的状态”,而不是“下一步具体要进行怎样的动作”,即,是“状态”描述,而不是“动作”。但是,可能是因为在课下作业中,主要是阅读已经给出的JML规格,写相应的代码,而完成代码部分似乎也与数据结构的图论部分关系更加密切,只是在一次课上完成过写JML规格的练习,所以,我更能阅读JML规格,而相对而言不太能写JML规格,易于存在不完善的地方。
总体而言,JML语言可以很好的用“面向对象”的思想描述一个函数的功能,在实际的编程、特别是合作编程中,具有极大的交流合作方面的可操作性,因此其重要性不言而喻,值得拿出一个单元专门训练。
第四单元:第一次作业基于不同element
在UML规格中的从属关系来设计架构。在构造顶层MyUmlInteraction
类时,需要对加入进来的每一个element
进行分析,判断它的ElementType
并根据parentId
以及其他性质决定它的存储位置。对于UmlClass
,UmlOperation
等,建立了MyClass
、MyOperation
类,用于存储element
自身的基本信息以及在后续需要用到的其他属性,并用HashMap
存储id、name与具体的类之间的映射关系。如:根据UmlClass
与UmlOperation
之间的从属关系,MyClass
类中也需要存储所有对应的MyOperation
类;根据继承与关联关系,MyClass
类与MyInterface
类中也有存有对应的父类信息以及关联类信息。对于如UmlParameter
、UmlAttribute等,
通过getparentid()可以找到其父类,由此可以将它们直接存储在对应的父类类中,如UmlAttribute储存在
对应的MyClass
中。对于UmlInterfaceRealization
、UmlAssociation
、UmlGeneralization
,它们表示的是class或interface的某种关系,我将它们所包含的关系提取出来,储存在对应的class或interface中。对于一般方法的实现,先找到classname
对应的class
,再调用MyClass
类中对应方法,返回结果或产生异常报告错误。对于与父类有关方法的实现,先调用classname
对应的class
自身的方法,再调用parentClass
的对应方法,在访问父类的父类(如果有的话);如此递归调用,逐级返回信息,最后得到结果并输出。
第二次作业在第一次作业的基础上,增加了对于顺序图以及状态图的处理,需要分析更多种类的UmlElement
。本次作业有关类图的操作可以直接继承第一次作业完成的部分;将interaction、lifeline、message归于顺序图,将state、pseudostate、finalstate、transition、region、statemachine归于状态图,其余全归于类图;在主要类中new出对应的图,将归好类的元素传入对应的图中进行构建和处理。Precheck的类图检查部分,引用并检查的就是在主要类中构建的类图。我新建立了UmlRegion和UmlStatemachine,对于类图、状态图、顺序图都有各自的
Interaction,对于类图的规则检查有therules
。
四、总结自己的课程收获
- 熟练掌握了 java (喝杯 java 冷静一下.jpg)
- 强大的抗压能力和极限debug能力
- 初步具备了对程序进行模块化处理
- 得到了“面向对象”的编程思想
- 深刻认识到了何谓“多线程”
- 学会了部署SMT Solver,并选择方法进行验证
- 学会了部署JMLUnitNG/JMLUnit,针对Graph接口的实现自动生成测试用例,结合规格对生成的测试用例和数据进行简要分析
- 学习了JVM的相关知识
- 学习了UML的相关知识
五、立足于自己的体会给课程提三个具体改进建议
在我心中,oo课程已经很成熟完善了,在此之外,一点点不成熟的小小建议:
- 希望能提升中测的完备性,让中测更全面一点,并提升中测成绩的占分比
- 希望指导书的陈述、说明能更加清晰完善
- 希望能在在线平台推出相关教程
- 感谢助教们、老师们一学期以来的辛勤付出!辛苦了!