【面向对象】第四单元UML总结及面向对象课程学期总结

一、第四单元的架构设计

第一次UML作业

第一次作业比较简单,仅包含类图的解析。正确理解UML元素的含义,以及每种UMLElement的各个属性的所指向的东西,就能比较容易地完成此次作业。

这里我构建了两个类:ClassRelations以及InterfaceRelations用来存储类和接口所包含的属性、方法等类图的基本信息,同时还要保存父类(父接口)、实现的接口,用来递归查找。是一种比较容易想到的实现方式。

类图如下:

1615407-20190623222826176-1396070410.png

为了加快对ClassName的查找和判断,使用了一个邻接表来存储同名的类,方便查找相应的类。

    private HashMap<String, ArrayList<UmlClass>> classNameMap = new HashMap<>();

    private void checkClassName(String className)
        throws ClassNotFoundException, ClassDuplicatedException {
        if (!classNameMap.containsKey(className)) {
            throw new ClassNotFoundException(className);
        } else if (classNameMap.get(className).size() > 1) {
            throw new ClassDuplicatedException(className);
        }
    }

第二次UML作业

第二次作业在第一次作业的基础上,增加了对UML状态图和UML时序图的解析。此外,还增加了三个模型有效性检查的规则,分别是:不含重名的成员、不能循环继承、不能重复实现接口。

与第一次作业相比,整体的架构变化不大,总的思想仍然是把每一种UML元素放到对应的UML图中,根据需求对这些数据加以维护。整体稍加改动后直接继承上次的作业。

本次实现的MyUmlGeneralInteraction直接继承了上次的MyUmlInteraction。对于状态图和时序图,与上次作业类似的,我创建了两个类StateMachineRelationsSequenceRelations用来保存与状态图和时序图相关的信息。

类图如下:

1615407-20190623222819612-57249102.png

对于模型有效性的检验

  • R001:针对下面给定的模型元素容器,不能含有重名的成员(UML002)

    与检测类名重复类似的,在ClassRelations类中,添加属性和关联对端的时候,对同名的属性和关联对端进行计数,就可以获得找到重名的成员。

  • R002:不能有循环继承(UML008)

  • R003:任何一个类或接口不能重复继承另外一个接口(UML009)

    这两个规则都是和继承或接口的实现相关的,所以放在一起处理。继承/实现关系构成了一个有向图,循环继承和多次实现接口的优先图,具有如下特点:

    • 循环继承的充要条件是:有向图中存在环(节点到节点自身存在一条路径)
    • 重复继承的充要条件是:有向图中,一个节点到另一个节点存在不止一条路径

    所以,将边权定义为优先图节点之间的路径数目,那么也就能同时判断是否存在循环继承和重复继承

    对于节点i到节点j的路径数目,对于中间节点k,存在如下的关系:route(i, j) = route(i, j) + route(i, k) * route(k, j)

    (貌似是动态规划的状态转移?有没有算法大佬可以讲一下,没学过不是很懂。顺便期待下学期算法有一个好的收获)

    使用UmlClassOrInterface接口对类和接口的关系统一建模,具体的代码如下:

      private HashMap<UmlClassOrInterface, HashMap<UmlClassOrInterface, Integer>>
                extensionGraph = new HashMap<>(); //包含继承关系和接口实现关系
    
      public static <T> void floyd(HashMap<T, HashMap<T, Integer>> graph) {
            for (T k : graph.keySet()) {
                for (T i : graph.keySet()) {
                    if (i.equals(k) || !graph.get(i).containsKey(k)) {
                        continue;
                    }
                    int ik = graph.get(i).get(k);
                    for (T j : graph.keySet()) {
                        if (k.equals(j) || !graph.get(k).containsKey(j)) {
                            continue;
                        }
                        int kj = graph.get(k).get(j);
                        if (!graph.get(i).containsKey(j)) { // 更新路径数
                            graph.get(i).put(j, ik * kj);
                        } else {
                            int newCnt = graph.get(i).get(j) + ik * kj;
                            graph.get(i).put(j, newCnt);
                        }
                    }
                }
            }
        }
    
      protected Set<UmlClassOrInterface> check008() {
            Set<UmlClassOrInterface> set = new HashSet<>();
            for (UmlClassOrInterface i : extensionGraph.keySet()) {
                if (extensionGraph.get(i).containsKey(i)) {
                    set.add(i);
                }
            }
            return set;
        }
    
        protected Set<UmlClassOrInterface> check009() {
            Set<UmlClassOrInterface> set = new HashSet<>();
            for (UmlClassOrInterface i : extensionGraph.keySet()) {
                for (UmlClassOrInterface j : extensionGraph.get(i).keySet()) {
                    if (extensionGraph.get(i).get(j) > 1) {
                        set.add(i);
                    }
                }
            }
            return set;
        }

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

架构设计是面向对象这门课的重中之重。由于我们的课程是按照单元推荐,每个单元的每一次作业都是在前一次的基础上,进行增量任务,所以一个好的架构可以让后续的作业更加容易完成。

  • 第一单元:多项式求导

    前两次的作业因为需求还比较简单,还算是有一个比较看得过去的架构。把多项式分解为单项式,再把单项式分解成幂函数和三角函数,一个多项式的求导问题就被分而治之。就是降低耦合,抽象出各个不同的类,将它们独立出来,再把每个对象组合在一起来统一处理。

    但是随着第三次作业的发布,这个架构设计的弊端就显现出来了。扩展性较差,导致第三次作业加上嵌套求导之后,几乎推倒重写。还是说明前两次作业对面向对象机制的理解不够深刻,以及对Java语言掌握不够熟练(不会使用接口泛型等特性)

  • 第二单元:多线程电梯

    多线程电梯调度主要就是要理解清楚生产者-消费者模型以及发布-订阅模型这些课上讲到的重要的多线程编程的设计模型。多线程最重要的问题就是线程的同步与互斥、线程间通信的问题。

    第二单元多线程电梯调度主要运用的就是“生产者-消费者模型”,构建一个两级的关系,通过共享队列在线程间传递信息。输入线程与调度器线程通过共享队列交互,调度器线程再与电梯线程通过共享队列交互。这个架构设计延续了三次作业,主要的变化都是调度器内部和电梯内部的调度算法。

    这个单元,课上老师也介绍了一些设计模式,比如单例模式、工厂模式等等。虽然实际写代码的时候没有特别注意使用这些设计模式,但是这些学习设计模式的思想,也是在知道我们怎么去设计类,怎么去设计接口,使得程序具有良好的扩展性和鲁棒性。当然,最重要的还是理解了多线程编程的方式和要点。

  • 第三单元:JML规格设计

    Java Modeling Language 是用于Java语言建模的语言,是一种契约化的设计思想。这个单元主要是围绕着图论展开的,由于有JML规格作为提示,整体比较顺畅。作业难度依次递增,从路径到地铁图层层递进。将不同的需求(最短路径,最少票价,最少换乘)分别用不同类的建模完成,降低耦合。由于都是图,所以把通用的图的算法单独提出来放到一个类,作为静态方法来调用(其实这样是有点危险的,一旦这里出错,所有地方都出错了,呜呜呜我就错在这了)

    JML为程序的开发设计提供了一个统一的规范规格,虽然估计JML实际应用不是很多,JML描述的规格,对方法、类等程序单元进行了严格的约束,这些正确详实的规格,相较于自然语言,能更加规范地描述需求,减少歧义,保证开发的速度与质量。这种”先设计规格再实现“的约定对于大型工程的协同开发有很多的好处。

  • 第四单元:UML图解析器

    这一单元主要的任务是解析UML图中的各种元素。理清楚UML各个元素的各个字段的意义以及之间的关系以后,推进的就比较顺利。

    根据UML图的组织形式,可以很容易地联想到仿照UML图地组织形式来构建每个类,按照类图、顺序图、状态图分别构建模型和存储相应数据。把实现的各部分划分成不同的责任单元,建立各个类来分别负责完成各自的任务。与第三单元有些类似。

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

  • 第一单元:多项式求导

    学习了讨论区编写测试脚本的方法,我采用如下的方式来发现别人的Bug:

    1. 把所有人的代码编译打包为.jar文件
    2. 编写测试数据(人工或自动生成),保存在testData.txt文件中
    3. 编写bash脚本,在Windows平台使用Git bash执行
    4. 编写Python脚本sympy用来计算标准答案,以及互测屋所有输出的答案
    5. 将第三步和第四步的数据输出保存到log.txt文件中,人工比对

    使用到的工具包括:Python Sympy, Git bash, VS code等。

    第一单元由于没有官方包的介入,输入输出都是自己处理,所以WRONG FORMAT是测试的重中之重。

  • 第二单元:多线程电梯

    多线程编程比较特殊,由于多线程并行具有不确定性,且不同的调度算法会造成不同的输出结果,所以不存在唯一的正确答案,评测机实现起来也有点复杂。所以我自测阶段,没有进行大量数据的测试。

    互测阶段,主要是读代码,看有没有出现一下几种问题:

    • 线程安全问题(共享资源有没有上锁)
    • 轮询,容易造成CPU超时
    • 调度算法,容易造成运行总时间超时
  • 第三单元:JML规格设计

    第三单元使用了很重要的一个测试工具:JUnit。

    单元测试是一个强有力的测试工具。相比大量数据的黑盒测试,JUnit单元测试可以在更快速的找到代码漏洞,花费更少的时间,达到很好的验证正确性的效果。JUnit还有一个有点是有测试覆盖率的指标,这是随机数据的测试所不能比拟的。

    除了使用JUnit进行测试,还构造了一个比较强的随机数据生成器来进行测试,与同学的输出进行比较。(还是晚了,发现Bug的时候已经截至了)

  • 第四单元:UML图解析器

    第四单元用StarUML花了几个比较特殊的图,比如重复继承、循环继承相关,以及带多的环的状态图等特殊情况,来测试代码的正确性,并重点测试了几个用到了深度优先搜索算法的指令。期末考试也比较忙,测试频次不是很多。

测试先行是我印象最深的。

无论是什么工程,写代码、连电路、焊板子这些,最重要的都是进行全面的测试。测试一定要和编写代码同步进行,或者先于写代码完成。第三单元最后一次作业惨痛的教训让我记住了“测试先行”这个道理,不要等到最后再匆匆忙忙测试然后提交,一个隐蔽的错误造成的可能就是满盘皆输。

四、总结自己的课程收获

  • 优秀的IDE是事半功倍的前提:IDEA真的很好用
  • 能较为熟练地使用Git、GitLab、UML等现代工具
  • 比较熟练的掌握了一门面向对象的编程语言:Java,以及良好的代码风格
  • 了解并掌握了多线程并发编程的编程方式和技巧
  • 收获了面向对象的程序设计思想、原则
  • 基本的软件测试意识、能力、方法技巧,以及如何做一个狼人
  • 软件架构设计意识和一些常用的设计模式,比如工厂模式、单例模式等
  • 抗压能力和加班能力没有周末,是为以后996做准备
  • 迭代开发的能力,以及作为一个成熟地乙方应该要安心接受甲方(助教组和老师)的需求变动

1615407-20190623222732059-1405296869.gif

一个学期的OO课程终于结束啦。就像爬山一样,现在到了山顶终于可以喘口气了。每周的作业走在催逼着自己不断前进,虽然一个学期基本没过过一个舒服完整的周末,但回过头来看,这一万多行代码,每个单元博客的总结记录,看得到这一路上的进步和收获。虽然有做的不尽人意的地方,但还是蛮有成就感的。

至少最后表彰总结课上没有空手回去?

五、对面向对象课程的建议

  1. 开个测试分享区,可以分享测试机或者测试数据

  2. 第二个是希望互测能有些改变,这个我在第一单元的博客中也写到过。

    这三次的互测后,我相对现在的互测制度提一点小小的建议:

    1. 仍然划分A, B, C三档,但是分组把这三组混合编组,例如8人间可以{2A, 3B, 3C},7人间{2A, 3B, 2C}
    2. 找到某个等级作业的Bug得对应等级得分,而与自己的作业等级无关
    3. 这样做得好处是:每个人都可以看到不同水平的代码,给C组和B组的同学更多学习的机会;避免了高段位“大眼瞪小眼”的尴尬,还有低段位“菜鸡互啄”的无趣;找到高段位的Bug更有成就感和分数奖励
    4. 缺点是:规则较为复杂,实现起来可能比较麻烦,而且不一定每个人都接受这种制度

    有同学说:“经过这三次互测,我的bash脚本和Python 写的比原来好多了。”

    当然,这些都只是我个人的想法,抛砖引玉,还请助教组学长学姐和老师们能研究出更好的制度,回归互测的本质。

    或者简单一些,把互测的人数减少到5至6人可能会合适些,这样可以多读代码,以免一些同学互测阶段直接放弃。

  3. 研讨课参与程度不太高,感觉“研讨”的氛围不是很明显。

  4. 感觉第三单元可以提到第一单元来。特别是多项式求导作为第一单元,而且最后一次作业,确实有点困难,不如先从JML开始,逐步熟悉Java语言和面向对象的建模方式。

转载于:https://www.cnblogs.com/migu3552/p/11069204.html

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值