OO第四单元博客

是终点,也是起点

有知有觉地来到了学期末,也意味着oo课程的学习将告一段落(没有理论考试必须好评!),但这只是旅途中的一站,在此经历的,都将伴随着一道前行,所有过往,皆成序章,oo只是一个起点,启蒙了架构设计方面的知识,后续还需潜心学习,争取更上一层楼。

一、总结第四单元的架构设计

这单元的主要困难在于指导书的理解和与IDE的斗智斗勇,其实也可以理解,毕竟语言的二义性是客观存在的,很难实现一个十分严谨的表述,虽然理解过程还是十分痛苦就是了。

架构方面,总体来说,就是疯狂改代码使符合checkstyle建立一个模型将输入的数据进行构造,这主要是通过java中功能强大的图机制来实现的。

输入元素的顺序问题

由于元素的输入并非按照逻辑顺序,将可能出现已经输入的元素中找不到对应的父类而出现喜闻乐见的NPE报错,需要先读入所有输入的元素,再按照逻辑顺序集中处理,下以类图的处理为例进行说明。

    private HashSet<UmlElement> associations;
   private HashSet<UmlElement> associationEnds;
   private HashSet<UmlElement> attributes;
   private HashSet<UmlElement> classes;
   private HashSet<UmlElement> interfaces;
   private HashSet<UmlElement> interfaceRealizations;
   private HashSet<UmlElement> operations;
   private HashSet<UmlElement> parameters;
   private HashSet<UmlElement> generalizations;

public void sendInElement(UmlElement element) {
       if (element instanceof UmlAssociation) {
           associations.add(element);
      } else if (element instanceof UmlAssociationEnd) {
           associationEnds.add(element);
      } else if (element instanceof UmlAttribute) {
           if (element.getName() == null) {
               wrong005 = true;
          }
           attributes.add(element);
      } else if (element instanceof UmlClass) {
           if (element.getName() == null) { wrong005 = true; }
           classes.add(element);
      } else if (element instanceof UmlInterface) {
           if (element.getName() == null) { wrong005 = true; }
           interfaces.add(element);
      } else if (element instanceof UmlInterfaceRealization) {
           interfaceRealizations.add(element);
      } else if (element instanceof UmlOperation) {
           if (element.getName() == null) { wrong005 = true; }
           operations.add(element);
      } else if (element instanceof UmlParameter) {
           if (((UmlParameter) element).getDirection()
                   != Direction.RETURN && element.getName() == null) {
               wrong005 = true;
          }
           parameters.add(element);
      } else if (element instanceof UmlGeneralization) {
           generalizations.add(element); } }

即完成输入数据的分流,用对应的容器存储起来,再后续按照顺序模拟输入场景完成构建。

类图

为了迎合后续增容的设定,将类图处理从umlinteraction中分离了出来,只需进行调用即可。该类图分析器存储了所有的新构建的类,便于之后添加元素时直接向已经实例化的对象中添加,解决了不一致的问题。

    private final Map<String, MyUmlClass> nameToClass;
   private final Map<String, MyUmlClass> idToClass;
   private final Map<String, Integer> nameToCount; //记录相同类名对应的类的数目
   private final Map<String, MyUmlAssociation> idToAssociation;
   private final Map<String, HashSet<String>> classToAssociatedClass; //记录类的关联类
   private Map<String, HashSet<UmlAssociationEnd>> classToAssociationEnd;
   private final Map<String, MyUmlOperation> idToOperation;
   private final Map<String, MyUmlInterface> idToInterface;

为了符合类图中的逻辑关系,“重写了”部分umlelement类,并通过逐层输入的方式,完成了模型的构建,接下来分别介绍各个重写类的架构,值得一提的是,由于assciation关联的是两个类,所以需要统筹管理,即在分析类中建立各个类之间的关联关系而不将其交给各个类分别管理,继承与实现也是同理。

class:用于存储所有类图里的元素
    private final UmlClass umlClass;
   private HashMap<String, MyUmlOperation> idToOperation;
   private Map<String, HashSet<MyUmlOperation>> nameToOperations;
   private HashMap<String, MyUmlAssociation> idToAssociation;
   private HashMap<String, UmlAttribute> idToAttribute;
   private HashMap<String, UmlAttribute> nameToAttribute;
   private HashMap<String, UmlGeneralization> idToGeneralization;
   private HashMap<String, UmlInterfaceRealization> idToInterfaceRealization;
association:用于存储对应的associationEnd
    private final UmlAssociation umlAssociation;
   private UmlAssociationEnd umlAssociationEnd1;
   private UmlAssociationEnd umlAssociationEnd2;
   private int end1 = 0;
   private int end2 = 0;

除此之外,其中还存有end1与end2是否存在的标志,供后续查询操作使用。

operation:用于存储对应的parameters
    private UmlOperation umlOperation;
   private HashSet<UmlParameter> umlParameters;
   private UmlParameter returnValue;
   private HashSet<UmlParameter> inParameters;
   private TreeMap<String, Integer> namedTypeList;
   private TreeMap<String, Integer> referenceTypeList;

其中存有本体,参数,以及与参数类型有关的容器。

interface:其实大体上与class并无区别,只是少了部分类别的元素输入
    private UmlInterface umlInterface;
   private HashMap<String, MyUmlOperation> idToOperation;
   private Map<String, HashSet<MyUmlOperation>> nameToOperations;
   private HashMap<String, MyUmlAssociation> idToAssociation;
   private HashMap<String, UmlGeneralization> idToGeneralization;
   private HashMap<String, UmlAttribute> idToAttribute;
   private HashSet<String> targets;
   private boolean wrong006;
   private int operationCount;
   private boolean hasDuplicatedGeneralization;

状态图

相比起类图偏复杂的结构。状态图倒显得纯粹得多,基本上是围绕region展开的,课程组为了简化也删去了一些内容(好评)

一共重写了两个类:region和transition

region:作为状态图的画布存在,存放一个状态图的所有内容。
    private UmlRegion region;
   private UmlPseudostate startState;
   private UmlFinalState finalState;
   private Map<String, UmlState> idToNormalState;
   private Map<String, Integer> nameToCount;
   private Map<String, UmlState> nameToNormalState;
   private Map<String, MyUmlTransition> idToTransition;
   private Map<String, HashSet<String>> stateToStates;//记录状态到状态的直接可达
   private Map<String, HashSet<MyUmlTransition>> signToTransitions;//记录状态到状态对应的transition
   private HashSet<String> idAlreadyList;//临时变量,用于记录已经查询过的状态
   private HashSet<String> idAnswerList;//临时变量,用于保存查询到的状态

除了存储必要的起点,终点,状态,迁移,还记录了一个状态可达的所有状态,以及状态间迁移对应的transition。

transiton:保存与迁移有关的内容
    private UmlTransition transition;
   private Map<String, UmlEvent> idToTrigger;
   private ArrayList<String> triggers;
   private Map<String, UmlOpaqueBehavior> idToEffect;
   private ArrayList<String> effects;

存储了迁移的本体以及触发方法和效果。

顺序图

顺序图的结构无疑是最简单的,只需要重写interaction并保存与其并列的attribute,这里涉及到attribute的归属问题,我的处理办法是将其同时输入给顺序图和类图的处理类,可知一定能在两者中的一个找到parent,找不到的自动抛弃就行。

interaction:与状态图的region类似,也充当画布的作用
    private UmlInteraction interaction;
   private Map<String, UmlLifeline> nameToLifeline;
   private Map<String, UmlLifeline> idToLifeline;
   private Map<String, Integer> nameToCount;
   private int lifelineCount;
   private Map<String, UmlMessage> idToMessage;
   private Map<String, UmlEndpoint> idToEndPoint;
   private Map<String, Integer> idToInCount;
   private Map<String, Map<MessageSort, Integer>> idToOutCount;

其中存有生命线,生命线之间的传输信息以及endpoint,还记录了每个生命线发出的消息个数。

至于算法,由于要求很低,实在是乏善可陈,就不献丑了。

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

1.层次的划分

抛开作业与作业之间的客观差距,明显可以看到第一次作业与最后一次作业在层次划分上的差异,甚至于第一次作业与第三次作业都存在较大的差别。

第一次作业:

OO第四单元博客

可以发现,架构设计仅仅是为了满足题目要求的层次划分,且mainclass承担了许多不必要的职责,各类之间的层次关系也仅为并列或不明确的层次关系。

第三次作业:

OO第四单元博客

明显感受到了差别所在,第三次作业将可能细分的所有部分实现了细分,并且各部分之间的关系是可以用一个图结构来完整表述的,但这样的设计也是在指导书的明示下才形成的,自身意识还不够。

最后一次作业:

OO第四单元博客

从一开始便按照各个元素的逻辑关系组织,并且把各个图的解析放到了不同的类中减少耦合性。

2. java自带功能辅助架构建立

刚开始建立架构时,多是自己编写一些方式来展现包含层次关系,后来则是熟练运用java的各种容器来辅助快速建立各种元素的关系。

3. 前瞻性

一开始的几次作业都是仅仅考虑满足当次作业的要求而不会思考之后可能的增添的功能是什么,导致出现多次重写的情况,不仅浪费了时间,也导致整个架构在头脑中并不清晰,debug的困难程度超级加倍。第一单元尤为明显:

OO第四单元博客

OO第四单元博客

OO第四单元博客

到了第二单元得益于课程组实验课代码的代表性,三次作业都在实验课代码的基础上修改,总归是少重写了许多内容。

到了第四单元则掌握了课程组安排内容的规律,在第一次设计架构时就将类图的处理分离出去,同时建立元素之间的层次关系,之后仅需考虑增添内容即可。

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

第一单元

前两次作业是使用样例数据与自己造的一点数据进行测试,有些碰巧的意味,最后一次作业则引入上两次作业的强测数据,会相对完备。

第二单元

第二单元的测试是使用自己构造的测试样例进行测试,但考虑到第二单元内容的特殊性,辅助以直接检查线程内容与执行关系来进行测试。

第三单元与第四单元

这两个单元的测试私以为最佳的方式还是逻辑的自然推导,在写完代码之后理清要实现的功能是否实现了,只要逻辑顺了,无需构造一些特殊的数据点也能实现较完备的检查。

四、课程收获

1.面向对象思想的培养

与课程名字呼应,这个学期下来,的确帮助我养成了初步的面向对象的思想,无论是编写代码还是日常生活中,遇到一件事情时,首先会抽丝剥茧,把其中的元素找到,然后分析元素之间的层次关系,最后得出解决办法。

2.java编程能力上升

其实上个学期也选修了一门与java相关的课,但碍于课时等客观条件的影响,最后效果不尽如人意,甚至最后的大作业都是用的java形式的面向过程方式编写的,对于java里的各种元素与关系并不熟悉,但这门课程帮助我掌握了java的基本用法,也理解了java的很多安排。

3.学习方法

本课程独特的考核机制启发了我对于学习方法的改进,每次互测时都是欣赏别人代码的好机会,每每都会感叹于dl们的精巧构思,虽然目前刀人数为0,但通过看别人代码得到的收获是一点分数无法比得上的。

五、几点建议

  1. 课程指导书的编写希望能够继续改进,指文字上的理解歧义问题,尤其是第三四单元对于指导书的依赖程度很高,出现许多理解上的问题影响都是十分大的(从讨论区的火热程度就能看出)

  1. 作业之间的联系与难度设置,按照基本规律来说,应该是逐渐变难且是一个递进的关系,但很多时候并非如此,尤其是第二单元,第一次作业属于地狱难度,但二三单元之间却十分简单,尤其是第三单元,甚至于不按照指导书要求的机制实现能够获得更好的性能。

  2. 架构问题。理解课程组希望同学们自行构造的想法,但很多时候很难设计出一个完美的符合后续要求的构造,重写是相当痛苦的,希望课程组能考虑在指导书中加入关于架构设计的提示,或者给出样例让同学们自行抉择。

结语

OO学习尚未成功,同志仍需努力!

上一篇:android – 在Scroll Flutter上隐藏Appbar?


下一篇:Java集合之Set接口