OO终章·GRAND BATTLE

 第四单元博客作业

不知不觉,这竟是OO课程的最后一次博客了,艰难的昆仑旅程也终于抵达了终点。开心是因为自己接受了十次作业的历练之后,工程开发能力提升了许多。心酸的是,以后不能每周写OO大作业了。

首先感谢课程组的各位老师和助教的辛苦付出,我真的收获很多,OO带给我的除了工程的方法和知识还有人生哲学。

Part1 :总结两次作业的架构设计

第一次作业UML类图

在第一次作业中,主要是针对类图的查询。所以我采用了组合模式来完成查询的需求。需求是针对类内部的各种信息(包括:属性,方法,关联,实现接口和顶级父类)的查询,所以针对这每一项内容,我建立了四个Helper来帮助类来管理相关的信息和查询相关的内容。

考虑到类与类之间的继承关系,可以采用dfs深度遍历一下,然后在回溯的时候将父类的信息传递给子类。(此处需注意:类只能单继承)考虑接口的继承关系也是如此(但是接口可以多继承,强测差点就死在接口多继承上了),可以采用深度搜索或者广度搜索算法,把父类定义的属性和实现的接口加入到子类。

     private void dfsDye(MyUmlClass myClass) {
        if (idToColor.containsKey(myClass.getId())) {
            return;
        }
        if (myClass.getParent() == null) {
            // 说明myClass就是顶级父类
            if (!colorToClass.values().contains(myClass)) {
                myClass.setAttrClassMap();
                idToColor.put(myClass.getId(), color);
                colorToClass.put(color, myClass);
                color++;
            }
            return;
        }
        UmlClass parent = myClass.getParent();
        MyUmlClass myParent = idToMyClass.get(parent.getId());
        dfsDye(idToMyClass.get(parent.getId()));
        // 把父类的信息传递给子类
        parPassToChild(myClass, myParent);
    }

对UML模型的深入理解

UML作为一种建模语言,对我们的架构设计有着很大的帮助。

类之间的关联关系

体现出一个类需要另一个类的协助才能完成自己的工作,用来管理相关信息,用来获取一些信息,当状态发生变化的时候需要通知对方。比如:在第一次作业中,需要查询类包含多少个属性的时候,ClassQuery就会找到与他相关联的AttributeHelper类,由后者返回上层需要的信息。

类与类的继承关系

继承是一种抽象层次的概念,父类可以概括子类的一些共性,子类扩展父类拥有自己的特征。继承关系建立好之后,子类便自动获得了父类的属性和方法,不需要在子类内部重新定义。构造这样的抽象带来的最大的好处就是可以减少Ctrl+C和Ctrl+V。例如:当一个方法既需要对子类1进行访问和操作,而且需要对子类2进行访问和操作,如果没有父类抽象层次的概括,那么在我们的实现中将出现大量的duplicate code,这会使程序看起来十分冗余。这种情况下我们可以使用接口或者父类进行抽象,在方法的输入参数中传递父类,然后利用多态机制,实现子类的特征。

例如:Class和Interface有许多共性,我们将其抽象为ClassOrInterface,使得我们的方法既可以访问和处理类也可以访问和处理接口。

UML模型中的树状结构

强测:未出现bug。

第二次作业UML类图

 

这次作业新增了顺序图和状态图的查询。

顺序图的UML树形结构

状态图的UML树形结构

主要难点

  1. 输入由多种形态,有顺序图、状态图、类图,它们之间可能存在元素的交集,在实现的时候必须区分相交的部分。

  2. 输入到输出的举例有些忽远忽近。类图的需求中,涉及到很多层次化的查询,比如:父类的属性要传递给子类,父类实现的接口需要传递给子类;接口可以多继承,接口的父接口也是其下层接口的父接口……这样复杂的层次结构需要大量的解析手段去将其一一剥离开再整合好。

  3. 存在各种样式的异常输入:类重名、属性重名、方法重名、状态重名……我们需要在实现中注意区分这些异常输入,识别和防范处理。

  4. 状态图中起始状态和终止状态不按照id区分而按照类型区分。

架构设计点

  1. 在模块间建立协同和交互行为,逐层代理调用,中心控制调用。

    中心交互类负责建立三个子查询类,使用组合模式和MVC模式将顶层的查询需求分散到各个子查询功能模块中。

  2. 在ClassQuery查询类下,再通过Check类检查UML三条规则

  3. 核心数据结构是由类/接口的继承关系和类实现接口三组关系构成的有向图,基于有向图进行信息的搜索。比如:求最大连通分量(Umlcheck008 : 是否存在循环继承);求可达顶点(一个状态的后继状态,顶级父类…)

个人感想

这次作业是我个人觉得写的最OO的一次作业,状态图、顺序图、类图之间的查询实现了完全的分离;由全局的交互界面统筹管理三种UML模型的查询。各类职责和分工明确,这样设计带来最大的好处就是在修改bug时不至于牵一发而动全身,当我定位到一个bug所在的局部范围时,我仅仅需要在这个局部范围内进行修改,影响范围不会涉及到上层的实现。

Part2: 架构&OO的演进

第一单元

多项式求导:逐步引入不同组合的项,逐步引入组合规则。

解决方法:构造抽象层次,进行归一化处理。

第二单元

电梯(傻瓜式->可捎带->多台可捎带)

引入多线程并发机制,架构需建立在线程控制、线程安全之上。

  • 设计要点:准确识别线程及共享数据,控制共享安全;

  • 通过研究课上的生产者-消费者模式,将其引用到电梯的架构设计中。电梯作为消费者,不断取走调度队列的指令,输入流为生产者,向请求队列中添加指令。

  • 调度器中维护一个调度队列是共享对象,在调度器中实现局部化调度算法的优化。

  • 架构时需注意:轻线程体设计+均衡的层次化共享数据设计,尽可能减小同步控制的范围。

 

第三单元——依据JML规格实现代码

进入第三单元,架构的训练要求变得十分重要,因为需要适应源源不断的新需求。从这次作业中,我得到的最大的感悟就是——“没有最优的架构,只有最合适的架构”,即:一切系统设计原则要以解决业务问题为最终目标,在设计架构时要明白需求的增长一定是一个循序渐进的逐步过程,我们设计不出完美的架构,但也不可能每次都重构。所以,为系统划分明确的职责边界,各个类之间应如何分工与合作显得尤为重要。

第三单元用到的最基本的数据结构是带权无向图,在此基础之上实现一个不同需求的地铁线路查询功能。

核心思想有以下几点:

  • 在图数据结构和地铁线路之间建立层次

  • 在数据之间建立映射关系而不知识简单的关联关系

  • 逐层构造,自底向上实现规定的接口和相应的规格。

第四单元:UML模型的理解与解析

核心思想:

针对不同类型的对象构造层次,在构造UML模型的过程中动态维护相关的查询数据(建立缓存,一次构建,数次查询)。

模型化设计

使用系统化的模型语言来表现设计结果,进而展开设计和思考。UML采用了视图与模型相分离的设计,在提供的diagram中表达相应的元素和关系。

UML四种模型视图:

四个单元的作业是一个循序渐进的过程,从一开始打开IDEA啥也不会到能独立完成一个UML的解析工具,个人对面向对象的概念和工程架构的概念有了更深入的认识。

海恩法则

 · 事故的发生是量的积累的结果。
 · 再好的技术、再完美的规章 , 在实际操作层面也无法取代人自身的素质和责任心 。

架构在OO设计中是必不可少的一环,架构并不像代码的实现,对就是对,错就是错,它并无对错之分,是一个取与舍的过程。架构提供了一组预定义的子系统、指定它们的职责,并且包括用于组织其间关系的规则和指导。

分层:对架构的横向划分

通过分层,逻辑上将子系统划分为许多集合而层间关系的形成需要遵循一定的 规则;通过分层,可以限制子系统间的依赖关系,使系统以更松散的方式耦合,从而更易于维护。

这个概念在计算机领域处处都有体现,在体系结构中,可以划分为用户层、系统调用层、操作系统层、设备驱动层、CPU处理器;在存储体系中可以分为内存、一级缓存、二级缓存、外存……

使用层可以带来许多好处

  1. 不需要了解上层或下层的实现,只需要关注本层次的实现是否正确。

  2. 当需要优化某一层的功能时,只需要保证与上下层接口的一致性,而不会影响到其他层次。

  3. 容易制定出层的标准,底层可以用来建立顶层的多项服务。

应用架构:

应用作为独立的科布署单元,为系统划分了明确的边界,也深刻影响着系统的部署和运维各个方面。在Java面向对象中,我们常常需要思考定义哪些类,以及类之间如何协同工作。

关键点

  1. 职责的划分:明确类与类的边界

    1. 逻辑分层

    2. 子类

    3. 核心类

  2. 类之间的协作

    1. 接口协议

    2. 关联

Part3 :测试的演进

——从白盒到黑盒,从手工捏造到自动化生产。

第一单元:求导规则

JAVA面向对象基础、多态与继承机制

第一单元的作业较为简单,代码量也不算大,因此采用的测试方式主要是针对一些特殊情况来构造数据;后期讨论区也有大佬开源了数据生成器和对拍器,可以方便的进行黑箱测试,然后利用matlab比较输出即可。

第二单元:多线程电梯

多线程设计、线程控制、线程安全

第二单元的主题是多线程,多线程并发程序的执行特点就是程序执行顺序和结果的不确定性,因此,无法采用传统的debug工具静态检查的方式进行测试。个人认为,多线程首先要避免死锁问题,这需要从逻辑上分析程序是否会出现循环等待、请求和保持、互斥和不可剥夺四个条件;其次,再检查程序的功能正确性,以电梯为例:需要检查能否按照设计的方向运行,能否在正确的时机捎带乘客,能否在正确的时机放下乘客……在最后一次作业时,通过大佬写的数据生成器和小 型评测机可以实现自动化的测试。

第三单元:JML规格与设计

单元测试、规格自动化测试

第三单元强调的测试手段就是利用Junit进行单元测试,使用单元测试可以完成对程序局部的检查,降低局部程序出现bug的风险。在完成单元测试的基础之上,再进行全局的黑盒测试,然后精确定位到局部范围内进行修改,重复该过程,程序的鲁棒性会提高许多。同时基于JML我们也接触了如SMT Solver和JunitNG等规格自动化测试工具,自动的生成测试样例,特别是对一些边界情况的检查,起到了十分重要的作用。

第四单元:UML模型解析

基于模型的单元测试

第四单元主要是针对UML顺序图、状态图、类图的解析,由于需求难度不高,未出现有两者重叠的情况,因此,可以通过构造不同的测试模型,进行单元测试。

Part4:总结课程收获

  • 培养了架构设计能力

    OO课程一直强调的几点:

    1. 不要急于编码,不要急于编码,不要急于编码!

    2. 合理设计架构,将细节的实现向后推迟。

    面向对象设计原则:

    1. 单一职责原则,一个类只负责一个功能领域的相应职责。

    2. 开闭原则,软件实体应对扩展开放,对内修改关闭

    3. 里氏代换原则,所有引用基类对象的地方能够透明的使用其子类对象。

      这一点在第三章中,求“最少XXX的时候用到”。

      子类通过继承父类的方法实现了多态,因此在调用父类对象方法时,可以自动实现子类方法。

    4. 依赖倒转原则,抽象不应依赖细节,细节应该依赖抽象。

    5. 合成复用原则,尽量使用对象组合而不是使用继承来达到复用的目的。

      这在最后一单元的查询层次体现得最为显著

  • 继承是类之间一种较强的交互手段,可以实现类的复用。通过类封装可以是新啊内部信息和实现细节的隐藏,而继承使得子类能够从父类得到成员变量和方法。比如:猫和狗都是宠物,它们都有宠物的一些特征(属性),如:年龄,体重等等;拥有宠物的一些行为(方法),如:会走,会叫。所以猫和狗可以抽象为宠物,它们自动获得宠物的特征和行为,并且拥有自身的特性,从而实现了多态。

    组合则是类之间一种较弱的交互手段,继承关系是类似A is B,而组合关系则形如A has a B,当一个类的功能或需求十分复杂的时候,可以考虑使用组合模式将其拆分,大事化小,小事化了,将其复杂度局限在一个较小的范围内,然后由一个控制类(关键类)来调用组合类的方法实现功能。比如:汽车的功能十分复杂,汽车必须有启动的功能,运行的功能,刹车的功能。如果我们仅仅在汽车的层面来思考如何满足这些功能,那工程量将十分冗杂而巨大;如果采用组合的思想,启动的功能交给发动机,运行的功能交给引擎和车轮,刹车的功能交给刹车系统……然后由汽车的控制系统同一管理这些系统,这样汽车功能被划分之后,将变得十分简洁明了。

  • 训练了良好的代码风格

    代码风格体现了开发者对于程序的专业态度,感谢OO这门课程对我们代码风格的严格要求。

    1. 代码的整洁

      空格和空行的合理使用。

      规范的缩进。

    2. 命名的规范——驼峰式命名,面向对象式命名

    3. 封装——保护类内部属性的隐私,防止外部调用者有意或无意的篡改

      • 隔离变化

        类内定义的属性是程序最容易变化的部分,如果我们将其开放给所有对象,很有可能出现一个对象的属性变化波及到另一个对象的情况,这是很糟糕的设计

      • 降低复杂度

        封装内部实现细节,只暴露最小的接口,会让系统变得简单明了,在一定程度上降低了系统的复杂度。(面向接口编程

  • 强化了OO的思想和方法

    ——万物皆对象

    与传统面向过程的思维不同,以后当我拿到一个工程需求的时候,按照OO的思想应该是先分析这个需求可能需要哪些类来完成,类内部应该有哪些属性和方法,类与类之间如何进行信息的交互和功能的协调。拿最简单的例子来说——“如何建造一架飞机”(这算是我面向对象思想的启蒙吧)。首先飞机可以分成驾驶舱,机舱,机翼,起落架,发动机……它们每一个都是OO中的一个类,有着各自的属性和功能。比如:驾驶舱需要有各种仪器,说到仪器,仪器又可以成为独立的类,因此在驾驶舱这个局部就有了分层的概念,驾驶舱其大小,有飞行员的座位,并且管理包含一系列的仪表,可以控制起落架,可以控制飞机速度,它的控制范围也就直接或者间接的成为了其属性。

    面向对象编程——一组对象相互沟通相互配合完成特定功能。

    软件开发苦苦追寻的一种境界是可重用性,可扩展性。面向对象是把属性和方法综合在一个里面。综合在一起复用的时候是整个对象进行复用。所以面向对象和面向过程相比,前者更加容易让我们达到可重用性。

  • 丰富了测试手段
    1. Junit进行单元测试

    2. 数据生成器+对拍器的大规模自动化随机测试

    3. 评测机的集成测试

Part5:课程思考与建议

建议一:指导书是我们编码的需求说明,也是完成作业的基础。但是在一学期的OO设计中,光是理解指导书的概念、把我的思维和指导书的思维融合在一起就需要消耗大量的时间。更有甚者,在提交了代码之后,发现有一个隐藏点一直过不了,然后与同学讨论之后才明白原来指导书的概念是这么回事。所以,希望课程组能尽可能的完善指导书中的概念,特别是一些边界情况、特殊情况的说明。

例如:在UML单元的第二次作业中,状态图的起始状态和终止状态的数目是不确定的,那么我们到底将其视作一个还是视作多个?这样比较模糊的边界会给同学们在编码的时候带来极大的困扰。

建议二:在公测前可以号召同学们组成一些学习小组,在组内分享测试用例以达到集思广益的效果。

建议三:在面向对象程序上手之前,或许可以发布几份比较基础的面向对象代码范例。在第一单元的作业中,编写面向对象程序一直无法摆脱面向过程的思维,后来在讨论课上听取了大佬的讲解之后,才知道面向对象程序的概念。这份面向对象案例可以是一些最基础的案例,或许其中还可以添加一些继承和多态??,只需要让新手知道面向对象程序是各个类组合完成功能而不是一道程序走到底就行。(因为本人在理解面向对象概念上吃了很大

原文地址:https://www.cnblogs.com/1-Gary-1/p/11057833.html