oo第四单元博客总结

一、第四单元两次作业的架构

最后一单元的作业中,主要是对UML图的理解并编写代码进行解析。

1、第13次作业

这次作业的任务是解析类图。主要的解析工作其实在官方提供的jar包已经完成了,我们需要做的就是把单个UmlElement根据id以及reference等建立起联系,将碎片化的信息重新构建出一个类图来,并完成相关信息的查询。

在本次作业中,涉及到需要处理的UmlElement的type有以下几种:UML_CLASSUML_INTERFACEUML_OPERATIONUML_ASSOCIATIONUML_ASSOCIATION_ENDUML_ATTRIBUTEUML_PARAMETERUML_GENERALIZATIONUML_INTERFACE_REALIZATION。这些类型组成了需要查询的类图信息,他们之间满足了树状结构,我所做的就是在一开始将输入的elements进行分类,将不同type的元素分到不同的链表中(根据待处理的不同元素类型,我建立了九个链表),再按照类图中本身的树状结构进行处理。对于这九种不同的类型,为了便于完成作业要求,我重新建立了MyClassMyInterfaceMyOperation三个类,而其余6个类型我沿用了jar包中定义的类。MyClass类用于表示类图中的一个类,因为类里面涉及到属性、操作、实现接口、关联,于是我在MyClass中分别设置了存储属性、操作、接口、关联的链表用于在建立图的过程中将属性等元素挂到MyClass中。MyInterface类用于表示类图中的一个接口,该类主要是用于存储该接口所继承的接口。MyOperation类用于表示类图中的一个方法,该类中主要存储了该方法中的各个参数。在完成了基本的分类后,再分别对9个链表进行处理,如对于属性链表,处理如下:

private void initattri() {
        //对属性进行处理
        for (int i = 0;i < attributes.size();i++) {
            UmlAttribute tmp = attributes.get(i);
            String parent = tmp.getParentId();
            if (idToClass.containsKey(parent)) { //找到这个属性对应的类
                idToClass.get(parent).putAttribute(tmp);//将这个属性挂到类中
            } //if不成立则说明是接口中的属性 不管
        }
    }

对于参数链表也是进行类似的操作,对于参数找到所对应的Operation,然后将参数挂进去。然后对方法链表类似,将其挂入对应的Class中。对于继承关系、关联关系、接口实现关系也进行分别处理,并在MyClass中存储相关信息。但注意,在这一步中,存储的都是直接继承信息、直接关联信息以及直接接口实现信息。

在完成上面的初始操作后,一些简单的查询就可以实现了,比如类图中包含的类个数、类自身包含的属性个数等,但对于涉及的父类继承下来的属性、关联、实现的接口仍无法处理,对于这类查询,我采用了递归查询的方式。以查询一个类的所有属性为例,当要查询这个类的所有属性时,我先去查询它父类的所有属性,当父类所有属性查出来时再返回给这个类,这个类将自己的属性和父类的所有属性合并起来,即得到了该类的所有属性,同时父类的所有属性也完成了查询。处理一个类的关联个数方法类同。而处理一个类所实现的接口时略微有一点区别,不仅仅从父类去看,还要去看这个类直接实现的接口有没有继承的接口,即需要分父类继承和接口实现两个方向去进行递归,最后合并得到这个类所实现的总的接口。

本次作业的类图如下:

 2、第14次作业

这次作业在上次作业的基础上,加入了对状态图和顺序图的解析。在本次作业中,类图、状态图和顺序图的解析是平行的,互不相干。因此,本次作业我直接继承了上次作业对类图进行解析的类,只扩增了对于状态图和顺序图的解析。除此之外,还增加了对于模型的有效性检查。

本次作业增加的需要处理UmlElement的type有UML_STATE_MACHINEUML_REGIONUML_STATEUML_FINAL_STATEUML_PSEUDOSTATEUML_TRANSITIONUML_INTERACTIONUML_LIFELINEUML_MESSAGE。整体思路和上次作业类似,我先将新增的这几个type进行分类,分别放入不同的链表中,然后依次对不同的链表进行处理。我新建立了MyStateMachine类、MyInteraction类、MyLifeLine类、MyState类。其中在MyStateMachine中挂了许多了不同的MyState表示该状态图中的类,对于state组成的链表,根据它所对应的region的id找到所对应的region,然后根据该region找到对应的状态图,然后将该状态挂入状态图中。对于transition组成的链表,我对于每一个transision去增加状态图中的转移个数,同时更新state的后继状态,。对于lifeline链表我也是找到它对应的顺序图,并将其挂进去。对于message链表,找到每个message对应的顺序图,然后去更新顺序图中的message个数,同时更新目的lifeline的incoming信息的个数。完成这些后,基本的查询就可以完成了。较为复杂的还是对于state的后继状态的查询,为了完成这个需求,我同样采用了递归的方法,对state的每个直接后继去求它的所有后继,然后返回,将这些后继都合并起来,完成对这个状态的所有后继的查询。

这次作业对于状态图和顺序图的解析还算简单,比较麻烦的是三条规则的检查:

R001:针对所有类,其成员属性和关联对端所连接的UMLAssociationEnd不能重名

针对这个规则,我定义了一个重名集合dupName,一个属性名集合attrNameSet,一个关联对端名字集合assoNameSet。每次往类里面挂属性时,就将名字放入attrNameSet中,同时将重名的属性名字放入一个dupName集合中;同理,每次放关联信息时,也将UMLAssociationEnd的名字放入assoNameSet中,将重名的关联名放入集合assoNameSet中。最后初始化结束后,再将attrNameSetassoNameSet求交集,即得到了属性名和关联对端名重名的集合,将其放入dupName中,所以得到了一个类中的重名集合。对每个类都进行如上的操作,将所有的重名集合合并起来,若发现该集合不为空,则说明模型违反了R001规则。

R002:不能有循环继承

针对这个规则,我建立了一个包含类和接口的有向图,每个类和每个接口都是其中的一个结点。类的继承关系在里面体现为一条子类指向父类的有向边,接口的继承关系也同理,实现关系也体现为一条有向边。在图建立完成后,对整个图进行环的检测,若存在环,则说明存在循环继承。环的检测具体如下:

    private void checkCycInherit() { //检查是否循环继承R002
        for (int i = 0;i < num;i++) {
            visit[i] = 0;
            instack[i] = 0;
        } //初始化标记数组
        for (int i = 0;i < num;i++) {
            if (visit[i] == 0) { //DFS
                dfs(i);
            }
        }
        for (NameId nameId:cycInherit) { //已经得到了nameId的set
            String id = nameId.getId();
            if (idToClass.containsKey(id)) {
                cycInheritRes.add(idToClass.get(id).getUmlClass());
            } else { //说明是接口
                cycInheritRes.add(idToInterface.get(id).getUmlInterface());
            }
        }
    }

    private void dfs(int node) {
        visit[node] = 1;//正在dfs
        top--;
        stack[top] = node;
        instack[node] = 1;//表示入栈了
        for (int j = 0;j < num; j++) { //遍历i点的邻接点
            if (matrix[node][j] == 1) {
                if (instack[j] == 0) {
                    dfs(j);
                } else if (instack[j] == 1) { //说明是个环,把环内的点都加入set中
                    int k = top;
                    while (stack[k] != j) { //栈往回找,找到j为止,形成一个环
                        NameId nameId = indexToNameId.get(stack[k]);
                        cycInherit.add(nameId);
                        k++;
                    }
                    NameId nameId = indexToNameId.get(j);
                    cycInherit.add(nameId);
                }
            }
        }
        top++;
        instack[node] = 0;
    }

其中cycInheritRes集合存储的是参与循环继承的接口或类。

R003:任何一个类或接口不能重复继承另一个接口

针对这一条规则,我采用的是在求一个类实现的接口或一个接口继承的接口的递归过程中,若发现上层违背了R003规则,则该类或该接口必定也违背了R003规则,同时合并上层实现或继承的接口时,若发现重复了,则认为该类或该接口违背了R003规则。最后将违背R003规则的类和接口都输出。下面以判断一个接口是否重复继承接口的代码为例:

    public ArrayList<MyInterface> getFathers() {
        if (fathersFlag) {
            return this.fathers;
        } else {
            for (int i = 0; i < directFathers.size();i++) {
                MyInterface tmp = fathers.get(i);
                ArrayList<MyInterface> tmpfahter = tmp.getFathers();
                if (tmp.getisDupRealize()) { //若父亲已经重复继承了接口,则this也肯定会重复
                    isDupRealize = true;
                }
                for (MyInterface tmp1:tmpfahter) {
                    if (fatherNameId.contains(tmp1.getNameId())) {
                        isDupRealize = true;
                    }
                    fathers.add(tmp1); //不管是否重复,总要加入
                    fatherNameId.add(tmp1.getNameId());//继承的所有nameid
                }
            }
            fathersFlag = true;
            return this.fathers;
        }

    }

其中isDupRealize为true表示该接口重复继承了,为false表示没有。

第14次作业的类图如下:


 

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

1、 第一单元

第一单元处理的是多项式的求导,三次作业层层递进。第一次作业是简单多项式的求导,于是我建立了一个类用于存储幂函数的相关信息并在里面配套了求导方法,然后在主类中完成合法性检测和各项求导。第二次作业在第一次作业的基础上加入了三角函数,即增加了新的因子,我建立了名为Item的类,里面包含了x、sinx、cosx的次数以及整项的系数。然后在ItemDer类中完成了求导功能。然后同样在主类中完成合法性检测和各项求导。整体变动较第一次作业变化步大。第三次作业的跨度就比较大了,引入了嵌套因子和表达式因子,我将整个表达式建立成为了一棵树的形式,子结点均为基本因子(整数、幂函数、无嵌套三角函数),中间结点为某种运算(加、减、乘、嵌套)。我通过自上而下构造表达式树。最后通过对根结点调用求导方法完成对整个表达式的求导。

在这个单元中,我对java从一窍不通到逐渐掌握,还了解学习了正则表达式的使用,以及对于超大数可使用BigInteger类等等。我也能明显感觉到自己的代码从第一次作业明显的c语言风格,到后面逐渐像面向对象语言的转换。总之这个单元让我正式的开始了解到面向对象的思维,对java开始有所了解。

2、第二单元

第二单元引入了多线程,同样是前两次作业跨度不大,但到了第三次作业就感觉到了难度的骤然提升。前两次作业都是单层调度的设计,到了第三次作业因为三个电梯的不同特性以及换乘要求等,我采用了双层调度的机制,一个主调度器里面挂三个电梯和三个子调度器,主线程不停的将请求放入总调度器中,主调度器根据各个电梯的特点以及当前的状态将请求分配给不同的子调度器,电梯在各自的子调度器的调度下执行请求。架构还是比较清晰,但对于多线程代码的书写,上锁就是一个很大的问题。

我在这单元中遇见最大的问题就是同步,因为我自己最开始没有完全完成好同步,导致我在测试阶段遇见的bug非常多,而且常常不可复现。通过不断的阅读代码以及调试,我终于找出了自己的多个bug,然后对上锁完成了修改。最终成功的完成了本单元的任务。

在这个单元中,我学到了多线程是如何实现的,以及同步性如何保障,锁应该怎么上、上在哪里、怎样能保证同步性的同时效率更高。同时这单元的面向对象思路显得更加清晰,一个电梯就是一个对象,每个对象处理自己的事,各个对象不互相干。

3、第三单元

第三单元让我们学习了程序规格,基本上来说,这三次作业就是看规格写代码,通过这三次的作业让我们对JML有了更深刻的理解。

这单元中,我认为最复杂的还是图结构的构造,以及如何将不同的问题抽象成一个同样的问题。在本单元的第三次作业中,需要查询多种问题,如最短路径、最少不满意度等,这种问题我其实最开始并没有想到很简洁的方法,后来通过和同学的交流以及翻看讨论区知道了怎样对这个问题完成统一。在这次作业中我还收获了滚雪球式的查询方式,通过这样的方法来提高后续查询的效率。

在这单元的作业我也更进一步了解到了面向对象的思想,第三次作业中,无论是查询什么,本质都是在一个无向图里进行查询,只是权重、拓扑结构可能有所不同,所以在这个图的类中进行专门的查询和存储结果是很好的。

4、第四单元

第四单元是其实就是根据已经解析好了的各个元素来重新构造出类图、状态图、顺序图,然后完成相应的查询。这单元的作业其实本身难度并没有前几次大,只要准确理解了UML就能够根据各个元素之间的相互联系将图重新的构造出来。这单元带给我最多还是学习了UML图在面向对象中的作用以及如何用它来总结一个面向对象程序。


 

三、四个单元中测试理解与实践的演进

我的测试主要还是分为正确性测试以及极端测试。前两个单元中,写到后面常常会写烦,只想着快点写完,靠测试程序来debug,但在第二单元中我就深深尝到了这种不负责任的想法的苦头。因为最开始写的时候没有处处留心处处注意,导致测试时反而会花更多的时间,得不偿失。所以后两个单元我每次写完后会重新浏览一遍我的代码,对照着设计思路好好看看,在这一遍的对照中,往往会发现一些笔误以及思路上的小错误,更改后再去进行测试就会发现debug的时间缩短了很多。

在测试程序编写这一部分,我还发现自己总是没有耐心去思考正确性的测试。但实际上这才是最根本的,有耐心去把需求的各种点都考虑到,然后写入自己的测试中,这仍然是我所缺少的能力,我在写测试时总是会缺少一些地方没有考虑到,我还需要自己多加锻炼这方面的能力。在极端测试这方面,我自己也会构造一些非常极端的情况,来计算程序运行的时间。在这四个单元里,我还要感谢同学们的互相分享,正是因为大家分享自己的测试程序,才能让我发现更多的bug,这种大家互相帮助一起进步的感觉真的很好。


 

四、课程收获

在这学期里,我的代码量可以说是我这三年里最大的一个学期,通过代码的积累,我的确收获了很多。首先,我基本掌握了java这门语言。其次,我学会了在写程序之前先进行一定的设计,而不是像以前写小代码一样,边写边改,同时在设计的过程中还要考虑怎样的效率更高,而不只是仅仅追求正确性,一个好的架构对于程序员来说是非常重要的。第三,我在这学期里实现了很多以前看到过的或听说过但从未实现过的代码,这无疑对我的代码能力是一次较大的提升,虽然我觉得我能力仍不是很好,但比起以前确实有很大的进步。第四,我意识到了系统测试的重要性,对于一个程序而言,基本的正确性测试和极端、边界测试都是必不可少的。

 


 

五、建议

  • 指导书的说明希望可以更加清晰一些。有时候理解错了,写完了代码再来修改真的很麻烦。
  • 关于实验课,希望能每次实验课之后将答案公布出来,只是上课考一下却不发布正确答案,同学们自己课下对答案也不一定正确,反而可能造成误导。
  • 中测开启期间,是否可以反馈学生代码的运行时间。因为学生自己在本地跑和提交上去跑毕竟平台不一样,时间上也会有差异。对于有时间限制的作业,如果能开放时间反馈,也能让学生及时优化自己的代码,提高性能。
posted @ 2019-06-23 14:13  gracciechou  阅读(200)  评论(0编辑  收藏  举报