OO第四单元小结暨2019春季学期OO课程总结

 

目录:

 


 

 

一、总结本单元两次作业的架构设计

 

1.1 第一次作业

分析需求:需要实现的需求有,查询UML类图中模型中一共有多少个类、类中的操作有多少个、类中的属性有多少个、类有几个关联、类的关联的对端是哪些类、类的操作可见性、类的属性可见性、类的顶级父类、类实现的全部接口、类是否违背信息隐藏原则。

类图

 

 

基本想法:初始化遍历给定的UmlElement时,用HashMap进行存储,并且在初始化时实先class➡class,class➡interface,interface➡interface以及AssociationList的初始化,以方便后来使用。对于需要实现的方法,分为两类:一类是初始化MyUmlInteraction类已经存储好的结果,如类中的操作,直接查询HashMap;另一类方法均采用递归循环的模板,如类的顶级父类,用递归的思想重复调用自身,直到找到TopParent退出循环,详细代码如下:

 
 1    public String getTopParentClass(String className)
 2             throws ClassNotFoundException, ClassDuplicatedException {
 3         checkClass(className);
 4         if (ansMap.containsKey(className)) {
 5             return ansMap.get(className);
 6         }
 7         UmlElement element = nameToClass.get(className);
 8         String re = className;
 9         while (true) {
10             if (!inheritClass.containsKey(element)) {
11                 re = element.getName();
12                 break;
13             }
14             if (!inheritClass.get(element).getElementType().
15                     equals(ElementType.UML_CLASS)) {
16                 re = element.getName();
17                 break;
18             }
19             element = inheritClass.get(element);
20         }
21         ansMap.put(className,re);
22         return ansMap.get(className);
23     }
24  


补充知识点:
  1. Java的类不采取多继承方式,但接口可以继承多个接口。类之间的单继承很好理解,是为了避免子类被引用的时候同一个方法无法判断应该使用哪个父类的方法;但是接口之间确实多继承的,因为接口下面的方法全部是抽象方法,没有任何的具体实现,即使继承的两个父接口包含同样的方法也没有任何影响。于是修改public List<String> getImplementInterfaceList(String className)函数如下:

  
 1   public List<String> getImplementInterfaceList(String className)
 2             throws ClassNotFoundException, ClassDuplicatedException {
 3         checkClass(className);
 4         UmlElement curInter = nameToClass.get(className);
 5         if (interMap.containsKey(className)) {
 6             return interMap.get(className);
 7         }
 8         ArrayList<String> result = new ArrayList<>(); //id
 9         //itself interface
10         if (curInter.getElementType().equals(ElementType.UML_INTERFACE)) {
11             ArrayList<String> result1 = new ArrayList<>(); //id
12             result1.add(curInter.getName());
13             return result1;
14         }
15         //interreals and father's
16         while (true) {
17             ArrayList<UmlElement> reals = classToInterReal.get(curInter);
18             for (UmlElement element : reals) {
19                 result.add(realToInter.get(element).getId());
20             }
21             if (!inheritClass.containsKey(curInter)) {
22                 break;
23             }
24             curInter = inheritClass.get(curInter);
25         }
26         //generalization
27         LinkedBlockingDeque<UmlElement> queue = new LinkedBlockingDeque<>();
28 29         for (UmlElement element1:inheritInter.keySet()) {
30             HashSet<UmlElement> fathers = inheritInter.get(element1);
31             if (result.contains(element1.getId())) {
32                 queue.addAll(fathers);
33                 while (!queue.isEmpty()) {
34                     UmlElement element2 = queue.poll();
35                     if (!result.contains(element2.getId())) {
36                         result.add(element2.getId());
37                     }
38                     if (inheritInter.containsKey(element2)) {
39                         queue.addAll(inheritInter.get(element2)); }
40                 }
41             }
42         }
43         ArrayList<String> finalRe = new ArrayList<>();
44         for (String id :result) {
45             String tmp = idToElement.get(id).getName();
46             finalRe.add(tmp);
47         }
48         interMap.put(className,finalRe);
49         return finalRe;
50     }

 

1.2 第二次作业

分析需求:在第一次作业的基础上,多添加了UML顺序图、UML状态图。需要实现的新需求有给定状态机模型中一共有多少个状态等等,分为三个接口UmlClassModelInteractionUmlPreCheck`UmlCollabrationInteractionUmlStateChartInteraction

类图

 

基本想法:对于顺序图和状态图接口的处理,采取与第一次作业类似的方法,即初始化遍历给定的UmlElement时,用HashMap进行存储,并且在初始化时实现相应需求的存储。但存储方法有所改变,将需求边视为一个图加边存储。例如对于状态图,Transition即是state之间的边。那么例如寻找后继状态,即看两点可达与否即可,具体代码如下:

 1     public int getSubsequentStateCount(String stateMachineName,
 2                                        String stateName)
 3             throws StateMachineNotFoundException,
 4             StateMachineDuplicatedException,
 5             StateNotFoundException, StateDuplicatedException {
 6         checkMac(stateMachineName);
 7         checkState(stateMachineName, stateName);
 8         if (substateCountCache.get(stateMachineName).containsKey(stateName)) {
 9             return substateCountCache.get(stateMachineName).get(stateName);
10         }
11         UmlElement reg = macToRegion.get(nameToMac.get(stateMachineName));
12         UmlElement curState = nameToState.get(stateMachineName).get(stateName);
13 14         ArrayList<String> result = new ArrayList<>();
15         HashMap<UmlElement,Boolean> visited = new HashMap<>();
16 17         for (UmlElement ele:allState) {
18             visited.put(ele,false);
19         }
20         //dfs
21         LinkedBlockingDeque<UmlElement> queue = new LinkedBlockingDeque<>();
22         queue.add(curState);
23         int i = 0;
24         while (!queue.isEmpty()) {
25 26             UmlElement element2 = queue.poll();
27             if (!result.contains(element2.getId()) && i != 0) {
28                 visited.put(element2,true);
29                 result.add(element2.getId());
30             }
31             if (stateTrans.containsKey(element2)) {
32                 for (UmlElement nextEle:stateTrans.get(element2)) {
33                     if (!visited.get(nextEle)) {
34                         queue.add(nextEle);
35                     }
36                 }
37             }
38             i++;
39         }
40         substateCountCache.get(stateMachineName).put(stateName,result.size());
41         return result.size();
42     }

 

本次作业中存在问题:

    1. UmlPrecheck中对于第二条规则的实现,在用DFS+染色方法寻找并打印环的时候,犯了一个低级的错误,取Arraylist的元素的时候忘记加index限制了。导致出错。这个错误应该是没有进行充分的测试导致的。

 


 

二、总结自己在四个单元中架构设计及OO方法理解的演进

 

2.1 第一单元

主题:正则表达式与多项式

这一单元主要围绕着这几个问题,正则表达式在JAVA中如何运用(并且注意避开stackoverflow)?JAVA中的封装、继承与多态?如何面对需求,剥离筛选出单一元素?

刚开始连JAVA编程都不熟悉,面对正则表达式确实有点懵。普通多项式的求导没有分离出多个类,直接按照C语言的面向过程的思想堆积到了一个java文件里,复用性与可扩展性都很差。第二次作业实现了三角函数类与多项式单项类,但对输入没有进行单独的处理;还有就是不正确性的判定,利用了长正则表达式,第三次作业完全没有办法使用。第三次作业难度陡然增大,于是新建了parser类,使用递归的思想,不断地对表达式进行剥离,并且在其中进行表达正确性的判断;当时没有好好理解继承的概念,也只抽取了几个基本元素的类,代码比较冗杂;另外,因为作业难度颇大,没有进行对于表达式的充分化简,只是求导出结果。

 

2.2 第二单元

主题:多线程与电梯

从单线程到多线程,多线程概念多而且难以理解,在上手打代码之前看了好几天的理论知识,也没太摸出头绪来。这一单元主要是有以下几点:

①.针对JAVA中多线程的使用,理解好经典的producer-consumer模式,其中线程安全与效率是最需要考虑的问题。关于线程安全,遇到的最多的问题是如何让线程正确停止执行。

②.观察者模式与工厂模式。观察者模式提供了一种对象设计,让主题和观察者之间耦合度降得很低,降低了需求与电梯之间的耦合关系。另外,第三次作业针对3部不同的电梯,可以进行注册再使用的方法。

第一次作业比较简单,因为单个电梯FCFS算法无需考虑效率问题,所以我只用了mainelevator两个线程,并且在后者内所有调用方法都加了synchronized前缀以保证线程安全。第二次作业我觉得应该是最难的,因为会涉及到线程安全的问题。好处在于实现了观察者模式,增加了调度池,避免了需求直接面向对象分配的尴尬场面。对于简单捎带的问题,我使用了mainelevatorschedulerrequestParser四个主要线程;在正确关闭elevator线程的问题上,我费了一些功夫,最后让三个线程共享一个reqList并在elevator中加入判断条件。如果第二次作业线程安全理解较为透彻,第三次作业其实大体框架并不难,尤其是对于我这种几乎没什么分配request调度的人(狗头)。对于电梯需求进行了朴素的拆分,然后查询与需求方向相同且可稍带的电梯,加入该电梯的等待序列。遗憾应该是没有实现很好的调度,并且没有通过Work Factory的方法对电梯进行注册,可复用性较差。

 

2.3 第三单元

主题:JML语言与地铁系统

相对于第二单元,这一单元就轻松了一点。这一单元的训练重点第一个是JML规格语言的理解,在此不赘述;另外,认为对于数据结构里图论的考察比较多?

前两次作业都比较简单,我也没有做太多的规格设计,只是单纯实现了需求接口;第三次作业复用代码时,才发现动不动就超过500行的checkstyle限制。并且对于最短路径直接套用dijstra算法,导致换乘出现问题。后来官方发布参考样例代码,才恍然大悟可以用工厂模式并且将图的算法提取成一个共通的计算类。对于图的拆点也觉得很巧妙,暴力选手留下了没好好学图论的泪水。

 

2.4 第四单元

主题:UML图

在代码架构上,私以为难度并不大。但是关于UML类图/状态图/顺序图的理解,starUML中的细节,JAVA中的继承实现机制等等细节问题要求较多。两次作业都比较简单,并且代码复用性也较好。

 

 


 

 

三、总结自己在四个单元中测试理解与实践的演进

 

前两个单元主要采取1/2的方式,通过大量的数据来发现bug。虽然可以测试出bug,但是不得不说对自己的程序却不适用,每次发现输出错误从头溯源debug的方式效率极低,这时候就引进了JUnit的测试方法。

 

测试方法1:构造边界测试样例并且实现对拍器

根据要求,自己脑洞构造边界测试样例然后肉眼debug,然后对拍就可以了。示例代码如下:

set addr="C:\Users\maltose\Desktop\result"if not exist %addr% (
    md %addr%
)for /l %%i in (1,1,1000) do (
    cd %addr%
    %build% >data_%%i.txt
    %correct% <data_%%i.txt >correct_%%i.txt%test1% <data_%%i.txt >test1_%%i.txt
fc test1_%%i.txt correct_%%i.txt >nul && echo >nul  || echo 1 not same %%i
​
%test2% <data_%%i.txt >test2_%%i.txt
fc test2_%%i.txt correct_%%i.txt >nul && echo >nul  || echo 2 not same %%i
​
%test3% <data_%%i.txt >test3_%%i.txt
fc test3_%%i.txt correct_%%i.txt >nul && echo >nul  || echo 3 not same %%i
​
%test4% <data_%%i.txt >test4_%%i.txt
fc test4_%%i.txt correct_%%i.txt >nul && echo >nul  || echo 4 not same %%i
​
%test5% <data_%%i.txt >test5_%%i.txt
fc test5_%%i.txt correct_%%i.txt >nul && echo >nul  || echo 5 not same %%i
​
%test6% <data_%%i.txt >test6_%%i.txt
fc test6_%%i.txt correct_%%i.txt >nul && echo >nul  || echo 6 not same %%i
​
%test7% <data_%%i.txt >test7_%%i.txt
fc test7_%%i.txt correct_%%i.txt >nul && echo >nul  || echo 7 not same %%i
​
%test8% <data_%%i.txt >test8_%%i.txt
fc test8_%%i.txt correct_%%i.txt >nul && echo >nul  || echo 8 not same %%i
​
%test9% <data_%%i.txt >test9_%%i.txt
fc test9_%%i.txt correct_%%i.txt >nul && echo >nul  || echo 9 not same %%i
​
set /a j=%%i%%10
if !j! equ 0 (
    echo %%i th
)

 

测试方法2:自动生成测试数据

限于水平,第一单元没有实现过多项式的自动生成测试用例。但第二单元的电梯测试样例比较好生成。

测试方法3:使用JUnit进行测试 & 结合Assert方法

Junit测试虽然相较于测试方法1&2结合繁琐了那么一丢丢,在互测范围内实用性也不太高。但是对于自己的程序来说,好就好在能够精确定位bug。对不同模块构造测试样例,然后用java自带的API中assert方法,能够精确定位,这样确实比一步步调试要方便的多。

 

 


 

 

四、总结自己的课程收获

 

  一学期下来,这门课程算是我学习的最艰难也最痛苦的一门课程了。虽然水平有限,但收获还是有的。从刚开始连JAVA语言都不熟悉,上来就开始写代码;到现在会考虑层层剥离元素,并且能够抽象出来元素,对几个OO原则也进行了一定的实践。课程过程中也发现,自己的数据结构基础确实薄弱,跟上进度很吃力。

  前几周因为一些个人心理上的原因,没有及时跟上进度。后来也因为这件事情有点反复情绪,努力了没有收获想要的结果会让人有些头痛。但关键时刻,荣老师也给了我很大的帮助,还有我身边的几个同学。刚开始设计阶段真的很难,我有很多问题,他们都耐心地给予了一一的解答。遗憾肯定会有,而且很多,但我没有什么后悔的,觉得我在我个人和环境的条件限制做到了我能做的吧。

  这学期最大的收获,是让我见识到了面向对象程序设计思维与架构设计的重要性。很多时候写不出来代码、存在需要修改大面积代码的bug,都不是因为具体实现的问题(通常只有几行),肯定是一开始你的设计就出了问题。就像人的组成一样,算法就像你的肉,而架构设计就是骨骼;如果没有一个结实而端正的骨骼撑起来,肉再健美也没有用。在我的经历中,一旦有了整体的框架,实现其实很快。还有一个比较私人的收获就是有点偏向生活吧,时间安排是一个人自律的重要表现吧,每次在DDL前拼死冲刺挺难过的。

 


 

 

五、立足于自己的体会给课程提三个具体改进

 

建议1:建议课程组先对课程时间安排进行调研,然后安排作业-互测时间

这学期OO和OS都集中在了周末,开始觉得时间有点紧;后来有一次调整到了周日开始,就觉得两门课时间有所错开了,时间也要有所宽裕。但是理论课的时间又不能变,导致一些很矛盾的结果。建议课程正式开始之前对学生的专业课时间安排进行了解,然后有可能申请教务排课之前就做出更为合理的时间安排?

建议2:适当降低单元中作业难度跨度/调整分配时间

每个单元的作业都是第三次作业与前两次作业难度跨度相当之大,但是通常又是一样的截止日期。如果下一次作业特别难,可以考虑把其中难以突破的重点提前预告,让学生有个准备与提前思考。

建议3:希望继续提供示例/优秀代码样例,供同学们交流参考

个人觉得这是个很好的方法。自己闭门造车越造越自闭,需要适当的交流与学习。阅读别人的代码,无论是架构上的启发,还时只是一点点实现上的小tips,我觉得收获都很大。

 


 

 

六、总结致谢

 

在此谢谢表示这一个学期以来所有老师和助教学长学姐的付出,大家都辛苦了。

尤其感谢:荣老师对我的帮助,还有我身边的小伙伴的帮助,不一一举例了。一分耕耘一分收获吧。

 

posted on 2019-06-24 16:38  teruzuki  阅读(149)  评论(0编辑  收藏  举报