【面向对象】第四单元总结

面向对象第四次博客作业

本单元两次作业的架构设计

  • 第十三次作业

    • 这次作业在设计时,首先考虑到由于要根据官方包提供的信息在不同的元素之间建立起关系,以方便查询,因此想到了对官方包所提供的几个类进行重新封装,这就产生了诸如下图中的MyUmlClass以及MyUmlInterface等以My开头的类名。

    • 其次,考虑到这几个类会公用很多索引关系,为了保存这些索引关系,进行全局的使用,我创建了一个名为MyStructManager的类对这些映射关系进行管理,同时也让这个进行管理的类负责了对整个模型的初始化的工作。例如我的MyStructManager中管理了这样一些结构:

      	private static HashMap<String, MyUmlClass> id2MyClass = new HashMap<>();
          private static HashMap<String, ArrayList<MyUmlClass>> name2MyClass =
                  new HashMap<>();
          private static HashMap<String, MyUmlInterface> id2MyIf = new HashMap<>();
          private static HashMap<String, MyUmlOperation> id2MyOp = new HashMap<>();
          private static HashMap<String, UmlAssociationEnd> id2End = new HashMap<>();
      
    • 这里我之所以将这个方负责管理的类的各个方法声明为静态方法,是为了满足实际需要,即不要每次都用这个类去实例化对象(因为这与实际“管理器”的职能是不符的),方便通过直接使用类名调用方法而不必实例化对象。

    • 基于以上的考虑,我的目录结构整体如下:

  • 其中主要的MyUmlClass类中包含了以下的内容:

    	private UmlClass thisClass;
    	private MyUmlClass superClass;
    
    	// HashMap<name, opSet>
        private HashMap<String, ArrayList<MyUmlOperation>> opMap;
        private ArrayList<MyUmlOperation> allOp;
    
        // `attr` is also judged by id, this class and super could have
        // different attrs with the same name
        private HashMap<String, String> attrId2Name;
        private HashMap<String, UmlAttribute> name2Attr;
        private HashSet<String> badAttrName;
    
        // `association` is judged not by name, but by id
        private HashMap<String, String> associationId2Name;
        private int endCnt;
    
        // `interface` is the same as `association`
        private HashMap<String, String> ifId2Name;
    
  • 第十四次作业

    • 这次作业在整体的设计时,考虑到设计的类比较多,同时彼此之间有比较清晰的界限,为了减少耦合,使结构更加清楚,我这次针对不同的模块使用包进行拆分,同时将属于不同模块的类进行拆分,这样做的同时,也改善了单个类的代码量,使得其更加平均化。

    • 首先是implementation这个包,这里面保存着我们要实现的全部函数的实现。由于指导书要求实现UmlGeneralInteraction接口,而这个接口同时继承了四个接口,因此我采用四个类分别实现四个接口,同时在这个实现指导书要求的接口的类中对其进行整合。

    • 其次是repackaging包,顾名思义,这个包保存的是所有我自己重新封装的类,而在其中又分为三个部分。

      • classmodels包,用存放类图检查与查询所使用的类。
      • collaborations包,用于存放顺序图检查与查询所使用的类。
      • statecharts包,用于存放状态图检查与查询所使用的类。
    • 最后是structure包,这个包用于保存所有与结构管理有关的类。进行结构管理的,一方面是建立的图,另一方面就是我在上一次作业中使用到的StructManager,我将这两部分分别划分为两个包存储。(这里需要说明的是,在存储图的包中,我并没有全部使用,其中为了保险起见我做了多种方案用于图的查询与合法性检查,其中每种图的构造和查询方法都使用了比较统一的接口命名方式,因此只需稍作修改即可实现在不同的方式构造的图中进行查询,例如)

      • public static void loadEdge(String id1, int type1, String id2, int type2) {
            // 具体实现
            // 这样的结构同时出现在三个图中,并且是静态方法,只要在我的MyStructManager中
            // 改变使用的图的类名,即可实现不同的图进行操作
        }
        
    • 具体情况如下图所示。

  • 这一次与前一次作业的拓展是我认为我在这几个单元的作业中实现的比较顺畅的,基本上将前一次的代码做少量的改动,适当根据这次的设计对结构进行微调,即实现了代码的重复使用。

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

架构设计的演进

虽然从实际情况来看,我这几个单元的作业完成情况不好,但是相对于我之前的设计而言,我认为单从个人角度而言还是有改善的。

在第一个单元,我对架构设计的理解主要侧重在了降低耦合的方便,希望通过对要求实现的功能进行拆分,划分成多个类来实现。然而当课程组公开了一些优秀同学的代码之后,我看到单纯从拆分类的角度而言是不足够的,还应当进一步减少类之间的耦合,也是在这是看到了那些优秀代码中对包的使用。不仅如此,我更意识到,单纯从实际意义上进行拆分是不够的,同时要注意从一个更加贴近实现的角度,或者说是从逻辑角度进行拆分。

在第二个单元,由于多线程的引入,我们上来的第一次电梯作业使用了生产者消费者模式,因此使用了比较定型的结构设计,即分成了三个类,生产者、消费者、调度器,在此后的设计中,基本上从整体而言都使用的这个结构。由于当时感觉这种设计思路比较显然,同时又比较符合课上的老师所讲的内容,因此没有做额外的结构设计。其实后来也得知,可以将一些优化调度的算法进行封装,将其从基本的生产者消费者模式中分离出来,会更加有利于扩展。

第三个单元,JML规格化设计。这一单元的目的在于读懂JML规格,并且实现官方包所提供的接口。在这一部分我吸取了前面的一些教训。由于第三单元第二次作业和第三次作业都涉及到了图的操作,因此我将图这个数据结构单独开类进行实现,将其与实际的操作分离。

第四单元,第一次作业结构上基本沿用前面的思路,从实际意义上与功能上进行分离。第二次作业是这几次作业中首次使用包,对程序进行更多层次的划分,同时实际的实现起来也感觉整个操作过程比之前更加清楚,虽然总体代码量不小,但是实现中也还算顺畅,同时由于一开始不太清楚关于三个UML的合法性检查的实现,由于采用了一些设计将这些与其他的查询功能分离开,在完成过程中能够先完成会做的部分,剩下的请教了一些同学,参考了他们的思路,最终总还算是没有延误时间,按时完成了。

对OO方法的理解

我在本学期刚开始的时候片面地认为OO就是一种进行分门别类,使用类来组织程序的思想。经过实际的练习发现,OO的方法包含了很多方面,而通过对结构的划分只是一种解耦的手段。

OO的思想,其目的在于提高程序的可维护性、可拓展性,通过一个更好的架构来让自己的程序变得灵活而整洁。而实现这种目的的一个整体思路就是对现实与实现程序的逻辑进行一个合理的抽象。

在实现这种抽象中,通过不断地实验与经验总结,也产生了很多比较成型的设计模式,比如生产者消费者模式、工厂模式等等,这些模式的初衷,也正是为程序提供一个比较成体系、结构清楚便于扩展的设计思路。

同时,正式由于在OO设计思想的指导下,程序有了越来越清楚的结构,因此也更加适合多人甚至是一个大型团队的开发。为了更加能够适用于这样的开发场景,我们需要一种高于代码层面,同时又比自然语言更有规范性的语言来描述规格,这是便用到了JML这样的语言以及UML这样的直观同时成体系的结构表示方法。

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

  • 第一单元
    • 其实在做第一次作业时,我根本没有注意到测试的意义,只是单纯地觉得,能够通过测评机的测试就算是对程序的测试。但是从第三次作业在强测中出现了错误的时候,我才认识到课下自己测试的重要性。
    • 在这一段时间,在讨论区中有很多非常厉害的同学分享了自己测试程序的方法,通过写一些自动化测试的程序以及数据生成和结果的正确性检查,用大量的随机数据测试达到测试的效果。这种方法在第一单元多项式求导而言我认为是比较有效的。因为有一些库提供多项式求导的功能,方便进行比对。
  • 第二单元
    • 在第一单元结束后,看到大佬们在讨论区分享的经验,自己也尝试写了一些测评,以及对电梯输出结果的正确性检查。对电梯输出结果的检查,由于指导书有比较详细的说明,有比较清楚的规范,因此也比较方便去检查,随机测试数据生成也相对容易。
    • 但是在测试过程中又遇到了一个问题,我在这一单元第三次作业公测中遇到了一个CPU时间超时。由于之前没有过多注意这个问题,因此上来不知所措。也是从这一次开始,我更加深刻地认识到了程序的正确性测试只是程序测试的一部分,程序的性能测试也很重要。这一次,我通过使用JProfiler,查看了各个模块各个方法对于CPU时间的占用,最终发现是我的等待唤醒机制出现了问题,并解决了这个问题。
  • 第三单元
    • 这一单元接触了规格化测试,通过指导书的介绍接触了JUnit,JUnit非常适合着这种单元化测试的场景,它能够将一个方法作为一个单元从一个完整的程序中分离出来进行有效的测试。这种场景我认为一般发生在当程序没有完全完成,还不能进行整体测试时,比如第四单元作业中,当指导书还没有发布完全版时,指导书上就写着可以使用JUnit开展单元化测试。还有就是这种只是实现模块,模块与模块之间独立性比较强的时候,都能够进行非常有效的测试。
    • 另一种是通过讨论区见识的。即基于JML自动生成测试用例的工具,例如JUnitNG。但是实际使用起来感觉经常会因为JML和其他各种原因产生很多错误,应用范围感觉也比较小,没有掌握这种方法。
  • 第四单元
    • 第四单元给我的一个直观印象就是将代码与直观的图结合起来。这一单元构造测试,可以与实际的图结合起来考虑比较极端的情况。

课程收获

经历了这一学期的OO课程,我看到了自己与其他同学还有很大的差距,也有好几次在强测中扣了好多分。不过与之前自己相比,我觉的这门课程让我收获了很多。

首先,我觉得在程序设计的思想上有所收获,开始关注程序的整体结构的清晰度以及架构的可拓展性,这种学习是在之前面向过程的程序设计中没有体会过的。

其次,我觉得这门课程是对我实现能力的锻炼。从第一次作业写一两百行的代码就觉得比较吃力,到最后一次作业程序的规模达到了1800行左右(当然包括注释,以及一些没有用上,只是为了通过其他方法实现作为一种可能的替代方案的代码(捂脸)),虽然这种代码量相对于一个“工程”的概念差的很远,但是至少是对自己的一个锻炼。

然后,通过非常厉害的同学们在班级群和讨论区的经验分享,也收获很多,比如开展自动化测试的方法等等。

除此之外,更少不了对于OO设计的经典的模式的学习,测试方法的学习,以及更有交流学习等等诸多方面的收获。

小小的一点意见

下面的建议只是我自己的一点看法,不合理之处还望见谅。

  • 希望调整每一次作业中中测与强测所占分数的比例。现在感觉中测所占比例相对于强测而言很少,一次作业的成绩感觉几乎很大程度上取决于强测。确实可以理解,这是希望锻炼自己测试程序的能力,但是有时真的是一时没有意识到问题,导致强测大面积爆伤,诚然,通过bug修复减少了很多扣分,通过hack也能挽回一些得分,但是这相对而言还是非常少的。希望适当提升中测难度,同时也适当提升中测所占比例。(同时,如果有可能的话,能不能请负责构造测试数据的助教学长分享一下测试数据的构造经验)

  • 希望每一次作业都能够公开标程或者请求公开一部分优秀同学的代码。本学期公开过标程,也公开过同学们写的非常好的代码,感觉收获很多。如果能够每一次,或者至少每一单元后两次作业的发布代码,我觉得能够收获更多。

  • 此外,关于每一次的博客作业,我想能不能放宽一些形式上的要求。因为有时感觉每次都按照固定的形式,有时会有一种陷入到形式主义之感,比如每次都通过IDEA提供的一些静态分析工具生成关系图、复杂度分析等等,我觉得能否适当放宽一些限制,鼓励更加自由的探索与博客撰写。

以上,谢谢。

posted @ 2019-06-19 19:53  MagicJim  阅读(220)  评论(0编辑  收藏  举报