OO_Unit4_单元总结&课程总结

OO_Unit4_单元总结&课程总结

Part1 第四单元作业架构

第四单元作业的主要任务是要我们完成一个UML解析器,这个解析器可以支持对UML类图、状态图和顺序图的分析,可以通过输入相应的指令来进行相关的查询,并且能够根据课程组给出的UML相关规则对输入的UML模型进行规范性的验证。

本单元的三次作业进行迭代开发,第一次作业只对类图进行分析,仅支持通过指令对类图进行相关查询;第二次作业中关于类图的部分相较于第一次作业没有变化,新增了对于状态图和顺序图的分析和查询;最后一次作业则是加入了规范性的验证,在实例化完毕后自动按序触发执行,而不需要通过指令的形式执行。执行中一旦发现不符合规则的情况,将直接退出,不进行后续有效性检查和指令查询

元素的解析顺序

通过三次作业的任务简述,易知本单元的作业架构在迭代过程中是始终如一的,并没有特别大的变化。其中非常关键的一部分是如何将解析后的UmlElements[] elements高效地、清晰地组织起来。

首先,我们需要明确两点:

  • UML图的数据是一次性输入完毕的,并不是在指令查询的过程中动态变化的。所以建模过程是一次性完成的,之后进行有效性检查和指令查询。
  • UML图不同类型的元素之间有着依赖关系,即UmlAssociation依赖UmlClass和UmlInterface,UmlEvent依赖UmlTransition。所以在进行建模的过程中不同类型的元素加入到Diagram的时间顺序是不同的。

用一下方法来对elements进行分类:

	//MyParse.java
	public void parserElements(UmlElement[] elements) {
        ArrayList<UmlElement> firstAdd = new ArrayList<>();
        ArrayList<UmlElement> secondAdd = new ArrayList<>();
        ArrayList<UmlElement> thirdAdd = new ArrayList<>();
        ArrayList<UmlElement> forthAdd = new ArrayList<>();
        ArrayList<UmlElement> fifthAdd = new ArrayList<>();
        for (UmlElement element : elements) {
            if (element instanceof UmlClass ||
                    element instanceof UmlInterface ||
                    element instanceof UmlInteraction ||
                    element instanceof UmlStateMachine) {
                firstAdd.add(element);
            }
            else if (element instanceof UmlAttribute ||
                    element instanceof UmlOperation ||
                    element instanceof UmlAssociationEnd ||
                    element instanceof UmlLifeline ||
                    element instanceof UmlEndpoint ||
                    element instanceof UmlRegion) {
                secondAdd.add(element);
            }
            else if (element instanceof UmlInterfaceRealization ||
                    element instanceof UmlGeneralization ||
                    element instanceof UmlParameter ||
                    element instanceof UmlMessage ||
                    element instanceof UmlPseudostate ||
                    element instanceof UmlState ||
                    element instanceof UmlFinalState) {
                thirdAdd.add(element);
            }
            else if (element instanceof UmlTransition) {
                forthAdd.add(element);
            }
            else if (element instanceof UmlEvent) {
                fifthAdd.add(element);
            }
        }
        addFirst(firstAdd);
        addSecond(secondAdd);
        addThird(thirdAdd);
        addForth(forthAdd);
        addFifth(fifthAdd);
    }

分类完成后,再通过优先级将不同类型的元素加入到我们的Diagram中。

元素的建模

虽然官方包中已经给出了各个类型元素对应的类和一部分方法。但是对于建立完备的UML图,并使其可以支持规范性检查和相应的查询功能,仅使用已有的数据结构和方法还远远不够。对此我创建了新的元素类,将该元素使用到的数据结构和方法进行封装,使每个元素可以存储更多的信息支持更多的查询功能。

具体实现如MyClass类:

public class MyClass {
    private UmlClass umlClass;
    //在类中维护我们所需要的变量
    private MyClass parentClass;
    //操作2 直接继承该类的子类的数量
    private ArrayList<MyClass> subClass;
    //操作3 类中操作的数量(不考虑重复操作)
    private HashMap<String, MyOperation> operationHashMap; 		//key is id
    //操作4 类中操作的可见性(不考虑重复操作)
    private HashMap<String, Map<Visibility, Integer>> operationVis; 	//key is name
    //操作5 类中某一操作的耦合度
    private HashMap<String, ArrayList<MyOperation>> operationCouple; 	//key is name
    private HashMap<String, ArrayList<Integer>> operationCoupleDegree;
    //存储错误信息
    private HashMap<String, Boolean> wrongType;
    private HashMap<String, Boolean> repeatOperation;
    //操作6 类中属性的耦合度
    private int attributeCoupleDegree;
    private HashMap<NameableType, ArrayList<UmlAttribute>> directAttribute;
    private HashMap<String, Integer> name2Attribute;
    //操作7 类实现的全部接口(直接或间接)
    private HashSet<MyInterface> directInterface;
    private HashSet<MyInterface> allParentInterface;
    //操作8 类的继承深度
    private int depthOfClass;

    public MyClass(UmlClass umlClass) {
        this.umlClass = umlClass;
        ...
    }

    public UmlClass getUmlClass() {
        return umlClass;
    }

    public boolean isCircular() {
        MyClass tmp = this;
        while (tmp.parentClass != null) {
            tmp = tmp.parentClass;
            if (tmp.equals(this)) {
                return true;
            }
        }
        return false;
    }
    
}

创建MyClass实例时,将对应的UmlClass传入。myClassumlClass是一一对应的。

查询指令

由于建模是一次性完成的,并不会在查询过程中动态变化。这意味着查询结果是永远不会发生变化的,所以我们将查询结果储存起来,以后每次相同查询都是O(1)的时间复杂度(不同于第三单元对图算法时间复杂度有着较高的要求)。

在具体实现上,大多图算法在第三单元都有涉及这里不再赘述。

有效性检查

模型有效性检查部分,将在实例化完毕后自动按序触发执行,而不需要通过指令的形式执行。执行中一旦发现不符合规则的情况,将直接退出,不进行后续有效性检查和指令查询

有效性检查中一部分可以在实例化建模的过程中进行检查,维护一个变量,如果在实例化的过程中发现了错误,则将该变量置为true,在检查时直接根据该变量值来抛出异常。另一部分则是需要在全部实例化完成后进行检查。

	//MyDiagram.java
	//实例化建模的过程中维护checkR001变量
	public void checkForUml001() throws UmlRule001Exception {
        if (checkR001 == true) {
            throw new UmlRule001Exception();
        }
    }
	//实例化完成后进行检查
    public void checkForUml002() throws UmlRule002Exception {
        Set<AttributeClassInformation> pairs = new HashSet<>();
        for (MyClass myClass : id2Class.values()) {
            UmlAssociationEnd anotherEnd = null;
            ArrayList<UmlAssociationEnd> thisEnd = reference2End.get(myClass.getId());
            if (thisEnd != null) {
                for (UmlAssociationEnd end : thisEnd) {
                    ArrayList<UmlAssociationEnd> ends = parent2End.get(end.getParentId());
                    if (ends.get(0).equals(end)) {
                        anotherEnd = ends.get(1);
                    } else {
                        anotherEnd = ends.get(0);
                    }
                    myClass.addAnotherEnd(anotherEnd.getName());
                }
            }
            String className = myClass.getName();
            ArrayList<String> attribute = myClass.getDuplicatedAttribute();
            for (String attributeName : attribute) {
                pairs.add(new AttributeClassInformation(attributeName,className));
            }
        }
        if (!pairs.isEmpty()) {
            throw new UmlRule002Exception(pairs);
        }
    }
    
	...

Part2 架构设计思维即OO方法理解的演进

第一单元

第一单元的主题是表达式的化简。这是我在四个单元中最煎熬、作业质量最差的一个单元。虽然彼时刚刚经历过课程组提供的pre的训练,但是表达式的抽象仍然给我的理解造成了很大的困难。第一眼看到题目的时候,我甚至有些诧异:这个题目和面向对象有啥关系?之后通过上机实验,老师的讲解以及身边大佬的帮助,我才逐渐明白可以将因子、项和表达式分别建类进行处理。之后紧接着,如何将表达式进行分析存储又成为了我的阻碍。我没有弄明白实验代码里递归下降处理表达式的原理,也没有想出自己的处理表达式的办法。最终采用了预处理的输入模式才顺利完成了作业。

第一单元初尝了较为抽象和复杂的面向对象编程,使自己的编程思想逐渐向模块化、结构化转变。明白了面对复杂问题,各模块明确的职能划分,之间清晰简洁的接口;模块内部有序的组织方式,无疑会使我们的程序更容易维护,更容易定位bug,更容易扩展。

第二单元

第二单元的主题是多线程编程。单元作业以电梯调度为背景,使用生产者—消费者的设计模式。

inputHandler线程与dispatcher线程分别作为生产者和消费者共享一个大的等待队列requestTableAll,而dispatcher线程又与各个电梯线程分别作为生产者和消费者共享一个小的等待队列parallelTable同步块设置就是在共享队列类RequestTable,RequestAll中,通过synchronized关键词将共享对象封装成为线程安全类,并且使用wait,notifyAll机制让调度器线程和电梯线程在恰当的时候进行等待和唤醒,避免轮询和死等。

纵观三次作业,整体的结构框架都以第一次作业的结构为基础,在增量开发的过程中只是经历了不大的改动,由此说明了好的结构框架有助于未来的扩展和迭代。在开始一个项目前,斟酌出一个较为完善的架构往往会达到事半功倍的效果。同时通过课上的讲授和实验课训练了解了多种设计模式,如生产者-消费者模式流水线模式单例模式,其中的代码框架非常值得借鉴和学习。在锁的使用方面,现在还只仅仅局限于使用synchronized关键字来进行加锁。对于其他类型的锁了解还很少,这需要在未来进一步的学习。

第三单元

第三单元的主题是根据课程组给出的JML规格进行编程。要求我们严格遵照JML规格来进行“契约式”编程。在进行方法编写时,只要给出的数据符合前置条件的要求,我们就应给出符合后置条件的结果。这种严谨的编程过程和编程完成后进行的全覆盖单元测试给我以后的编程提供一个良好的范式。

在我们实现的社交网络中,NetWork为图,Person为节点,relation为边。方法addPerson为整个图增加节点,addRelation为节点间添加带权值的边。在充分理解规格的同时,如何更便捷的组织数据,降低运算的时间复杂度也是需要我们考虑的地方。在图模型的构建和运算中,运用并查集、优先队列等数据结构可以有效降低时间复杂度,我也由此意识到好的数据结构和算法往往可以简化操作提高效率。

第四单元

第四单元的主题是完成一个UML解析器。重点在于理解UML图中各元素之间的关系,具体的架构就是依据元素间关系实现的树型结构。第一部分有着详细的描述,这里不再多讲。

Part3 测试理解与实践的演进

第一单元主要通过自己手动构造测试数据,通过划分不同的情况构造对应的数据来进行覆盖。同时考虑可能会出现的边界情况,有针对性地构造特殊的数据,如三角函数部分\(sin(0)\),\(cos(0)\),\((sin(x))^2+(cos(x))^2\)等需要化简情况。手动构造数据数据量较小,难以做到完全的覆盖。

第二单元从讨论区白嫖到了某大佬第一次作业的测评机,包含随机数据生成、数据投喂和结果正确性验证三部分。我在本单元主要通过测评机大量投喂随机数据来测试程序的正确性。但是随机数据无法很好地测试极端情况下程序的性能,比如互测时在70S投喂大量从一楼到十楼的极端数据。因此在随机数据测试的同时,也手动构造了一些极端数据来测试程序性能。

第三单元通过生成随机数据之后与小伙伴对拍来进行测试,通过随机数据进行测试可以做到指令的完全覆盖,但是在第二次作业在极端数据上翻了车。在最初考虑性能问题的时候,只把目光放在了需要运用图算法的方法上,诸如:isCirclequeryLeastConnectionsendIndirectMessage,而忘记考虑其他算法的时间复杂度,同时又忘记构造针对性的极端数据来进行测试,导致在互测中被Hack。

第四单元作业做的测试较少,仅是通过情况划分手动构造对每个查询指令的测试数据。

Part4 课程收获

经过本学期OO课程16次作业、实验、研讨课的锻炼。我对于面向对象编程、多线程编程、设计模式、单元测试等等的概念从无到有的建立起来了。学习的过程就是一个思维转变的过程,如计组课程中在搭建各个模块的过程中理解“高内聚,低耦合”的设计思想;在操作系统课程中理解虚拟内存机制为“丑陋”的硬件创造了一个“理想国”。在面向对象的课程中,我们的目光已经不在局限于使用代码实现一个细微的过程,而是着眼于全局将工作分解为不同的模块,使用模块来搭建我们的“大厦”,我们既需要注意模块的内部构造同时又要处理好模块间的协调关系。

面向对象课程仅仅是师父领进门,我与“面向对象”也仅仅相处了短短一个学期;我对于多线程编程也仅仅停留在使用synchronized关键字进行加锁,通过wait,notifyAll机制使进程等待和唤醒;我对于设计模式的了解也局限于单例模式、工厂模式、生产者消费者模式;我还没有真正搭建过一个自己的测评机......所以说面向对象的课程不仅没有结束,它其实才刚刚开始。

Part5 课程建议

  • 希望课程组可以在每次上机实验结束后给出实验的答案。每次上机虽然时间短暂,但是都是紧张并且高效的。上机实验的题目经过了同学们的充分思考,同学们肯定会有这样那样的疑惑。如果不能在上机之后给出实验的答案,那么上机实验的训练作用会大打折扣。
  • 希望课程组能对搭建测评机和进行单元测试给出具体的指导。
  • 加强对研讨课上小组讨论主题的引导,可以提早放出研讨课主题并要求同学们在研讨课前进行相应的准备工作。
posted @ 2022-06-29 11:51  Selabarsayes  阅读(14)  评论(1编辑  收藏  举报