2022-面向对象设计与构造-第四单元总结

2022-面向对象设计与构造-第四单元总结

本单元架构设计

层次结构

本单元作业层次非常明确,根据startUML中的层次进行建模即可。官方包帮我们解析好并封装了 UML 中的各类元素,但是不能扩展,因此我又将一些必要的 UML 元素进行了自己的封装,各个类的层次关系如下

MyImplementation
|-  MyClass
|   |-  MyAttribute
|   `-  MyOperation
|       `-  MyParameter
|-  MyInterface
|-  MyStateMachine
|   |-  MyState
|   `-  MyTransition
|-  MyInteraction
|   `-  MyLifeLine
`- MyNetwork

除了 MyNetwork 类,其它类都是对应 UML 中的各种元素。

实现细节

MyNetwork 类是单独用来管理类和接口的继承关系的,主要用于建图并检查循环继承和重复继承。具体的检查方法:首先将全部的类和接口作为点加入图中,然后把继承关系转化为子节点到父节点的一条有向边加入图中,对循环继承,使用 dfs 搜索图中的环,一旦检查出环就加入一个用于存放循环继承元素的 HashSet 中;对于重复继承,对每个节点使用 dfs 搜索其父亲,一旦发现某个节点有重复的父亲就将这个节点加入用于存放重复继承元素的 HashSet 中。

本次作业是一次性读取所有 UML 元素,之后不会进行任何修改,因此可以先进行必要的预处理,将模型建立、整理好,这样能大大减少处理询问的时间,减少重复劳动,提高效率。我把预处理进行拆分,放到了构造函数中,具体流程如下:

  • 首先对类图处理(这样是最复杂的一部分)
    • 遍历一遍,将所有的 UmlClass, UmlInterface, UmlAttribute, UmlParameter, UmlOperation, UmlAssociationEnd 解析出来放到容器中备用
    • 遍历一遍,将所有的UmlAttribut, UmlParameter 和它的 parent 联系起来,
    • 遍历一遍,将所有的 UmlOperation 和它的 parent 联系起来
    • 遍历一遍,将所有的 UmlGeneralization, UmlInterfaceRealization, UmlAssociation 对应的关系建立起来
    • 在检查完 R003(循环继承)之后,从所有的 UmlClass, UmlInterface 的根父节点开始向下递归地整理和继承有关的东西(如继承深度、继承的属性等),这里之所以放到检查循环继承之后是因为如果存在循环继承,那么递归时会出现爆栈
  • 然后对顺序图处理
    • 遍历一遍,完成对所有的 UmlInteraction 的封装
    • 遍历一遍,将所有的 UmlLifeline, UmlEndPoint 加入对应的 UmlInteraction
    • 遍历一遍,将所有的 UmlMessage 在对应的 UmlLifeline 中记录下来
  • 最后对于状态机图处理
    • 遍历一遍,完成对所有的 UmlStateMachine 的建模
    • 遍历一遍,将所有的 UmlRegionUmlStateMachine 绑定起来
    • 遍历一遍,将所有的 UmlState, UmlPseudostate, UmlFinalState 建模并与其 parent 建立联系
    • 遍历一遍,将所有的 UmlTransition 加入其中
    • 遍历一遍,将所有的 UmlEvent 和其 parent 绑定起来

代码风格问题

本次作业一个很令人头疼的问题是 styleChecker,由于要实现的那个类方法太多,而且需要导入很多东西(仅import的内容就占了50行),所以很容易行数超出限制,导致代码风格分“啪”一下就快没了。我解决这个问题主要有以下四种方法:

  • 把相同的部分进行封装。例如在类图查询中,很多方法基本上都要先检查是否存在这个类、这个类是否有重复并抛出异常,我们可以用一个方法根据名字获取对应的类并顺带完成检查并抛出异常功能,代码如下:
    private MyClass myGetClass(String s) throws
            ClassNotFoundException, ClassDuplicatedException {
        if (classNameMap.get(s) == null || classNameMap.get(s).size() == 0) {
            throw new ClassNotFoundException(s);
        } else if (classNameMap.get(s).size() > 1) {
            throw new ClassDuplicatedException(s);
        }
        return classes.get(classNameMap.get(s).stream().findFirst().orElse(null));
    }
  • 层次化设计。在外层,我们只做找到特定对象并调用该对象的特定方法的工作,剩下的交给那个对象自己完成,就像《Thinking in Java》中说的“给对象发消息”这种方式,例如:
// MyImplementation
	public List<Integer> getClassOperationCouplingDegree(String s, String s1) throws
            ClassNotFoundException, ClassDuplicatedException,
            MethodWrongTypeException, MethodDuplicatedException {
        return myGetClass(s).getOpCoupling(s1);
    }
// MyClass
    public ArrayList<Integer> getOpCoupling(String name) throws
            MethodWrongTypeException, MethodDuplicatedException {
        // ......
    }
  • 使用 idea 自动压行。当我们把鼠标悬停在一个循环上 idea 会在左侧给出快速修改方案,我们可以用这个来快速压行,非常的 intelligent,例如:
// 修改前
	ArrayList<String> ans = new ArrayList<>();
	for (MyInterface myInterface : myGetClass(s).getInterfaces().values()) {
		ans.add(myInterface.getName());
	}
	return ans;
// 修改后
	return myGetClass(s).getInterfaces().values().stream().
		map(MyInterface::getName).collect(Collectors.toCollection(ArrayList::new));
  • 将部分功能交给单独的类去处理。例如在检查循环继承和多重继承时,我使用了 MyNetwork 类管理继承关系并给出结果

在综合运用了这些方法后,在 MyImplementation 中很多方法都可以只用一两行解决,例如类图相关的查询方法:

    @Override
    public int getClassCount() {
        return classes.size();
    }

    @Override
    public int getClassSubClassCount(String s) throws
            ClassNotFoundException, ClassDuplicatedException {
        return myGetClass(s).getSonSum();
    }

    @Override
    public int getClassOperationCount(String s) throws
            ClassNotFoundException, ClassDuplicatedException {
        return myGetClass(s).getOwnOpSum();
    }

    @Override
    public Map<Visibility, Integer> getClassOperationVisibility(String s, String s1) throws
            ClassNotFoundException, ClassDuplicatedException {
        return myGetClass(s).getOpVisibility(s1);
    }

    @Override
    public List<Integer> getClassOperationCouplingDegree(String s, String s1) throws
            ClassNotFoundException, ClassDuplicatedException,
            MethodWrongTypeException, MethodDuplicatedException {
        return myGetClass(s).getOpCoupling(s1);
    }

    @Override
    public int getClassAttributeCouplingDegree(String s) throws
            ClassNotFoundException, ClassDuplicatedException {
        return myGetClass(s).getCoupling();
    }

    @Override
    public List<String> getClassImplementInterfaceList(String s) throws
            ClassNotFoundException, ClassDuplicatedException {
        return myGetClass(s).getInterfaces().values().stream().
                map(MyInterface::getName).collect(Collectors.toCollection(ArrayList::new));
    }

四个单元中架构思维及 OO 方法理解地演进

第一单元

本单元主要是一种抽象、层次化的架构思维。在第一单元作业中我设计了 Factor 接口,并建立了包括三角函数因子、幂函数因子、表达式因子等多种因子,它们虽然有着不同的特性,但是都可以进行运算、展开,在外层我们无需关注这些因子的具体类型,只需要将其抽象成 Factor 进行操作即可。对于函数调用,我第二次作业是使用的字符串替换完成,在第三次作业中大规模重构改成了层次化的自上而下展开,使得代码更加健壮、结构更加合理。此外,通过本次作业我还学习了递归下降的解析方法,虽然开始学习的时候挺痛苦的,但是学会儿感觉非常好用,而且对下学期的编译原理也会有所帮助。

本单元我对 OO 方法的理解主要是使用继承、实现、多态、抽象这些机制,通过这些机制,我们可以更加方便地去管理不同的类,写出结构更清晰、行数更少的代码。同时,还有使用容器的方法,在这一单元中我尝试了 ArrayList, HashSet, HashMap 等多种容器,在展开和化简中起到了重要作用。

第二单元

本单元主要是生产者消费者——消费者模型的多线程编程思维。将请求解析器所谓生产者,电梯作为消费者,请求队列作为缓冲区就建立了最简单的生产者——消费者模型,不同类之间调用相应的方法,像发消息一样进行信息的传递,来实现共同协作。在最后一次作业,我还使用了流水线模式和单例模式,整个单元下来对多线程和相关的设计模式有了一个初步的了解。

在 OO 方法上,主要学会了使用 synchronized 关键字来完成线程之间的互斥访问,还有使用守护条件、wait, notify 来避免轮询。

第三单元

本单元主要是根据 JML 规格来编写代码,大的架构都已经给定了,自己的架构都比较简单。但是本单元中需要我们先整体地阅读所有的规格,对所有的功能有了一个初步的了解后再进行构思,思考实现细节,最后完成编程,即从宏观到微观的架构思维,这在完成大型工程项目中是十分重要的。

在 OO 方法上,学会了契约式的编程方法和思维。契约式的思维主要体现在对规格的描述方式上,课程给出的 JML 都是采用前置条件、后置条件和不变式的这种方式描述的,这种描述方法非常利于进行单元测试。

第四单元

本单元的架构思维重点在于层次化设计。我们要实现的 MyImplementation 需要管理非常多东西,实现非常多功能,将这些功能都交给它会导致这个类非常复杂,一个更好的方法是采用层次化设计,建立多个类,这些类之间相互协作,每一层只完成它自己应该完成的工作,然后调用下一层的方法把任务交给下一层。这样,方法自上而下调用,异常自下而上传递,整个层次非常清晰,每个类复杂度合理,也很容易在出 bug 时快速找到”罪魁祸首“。

OO 方法上主要体现在“给对象发消息”的方法上:我们要完成某项功能但是功能难以由自己直接完成时,可以给相应的对象发消息,请求它去协助完成;接收到消息的对象发生异常时,如果不能自己处理需要将异常消息报告给发送消息的”上级“。

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

整个课程的测试我都是以测评机自动测试为主,就主要讲下自动测试的理解和实践演进

第一单元

本单元我的测评机相对简陋,数据生成器根据文法去生成数据,并进行一定程度的限制;正确性判定则采用现有的库来完成。这一单元测评机有很多问题,生成的数据很多都不满足互测限制,在互测时不能直接用;有时候会遇到因展开式太长导致的测评机卡住的问题,需要在人的监视下工作等等。因此,测评机实际上自动化程度较低,是测试的一个辅助手段,很多时候需要在测评机发现问题的基础之上进行分析、拆解。测试思路是使用大量随机生成的数据来进行测试。

第二单元

本单元我的测试技术有了飞跃!

从数据生成手段来说,我采用了数据分类的方法,根据空间密集程度和时间密集程度分成若干个类,然后用不同类型的数据。例如,有请求密集、请求稀疏、请求疏密交替的数据,有只含特定楼层或落座横向或纵向请求的数据,有只含横向或纵向请求的数据等等,从测试结果可以看出分类确实能有效提高 hack 成功概率,用随机生成的数据可能几百组都hack不到的问题,使用特定类的数据几组就能测出来。

从评测技术来说,我的评测机能够实现检测多种错误、并发测试、超时强制结束程序、给出错误原因等多种功能,自动化程度很高,并且部署到了服务器上。

因此,我测评机已经成为了我进行测试的主力。

第三单元和第四单元

这两个单元相对于第二单元在测试方案上没有太大的改进,数据生成方面仍然使用了数据分类的方式,正确性判定方式改成了多人对拍。

课程总结

这一学期 OO 课程的学期可谓是一个“涅槃重生”的过程。

“涅槃”体现在课程难度大,作业花费时间多,bug 给我带来了很多烦恼。印象最深的是第一单元的前两次作业,但是看到那么复杂的题目真的差点儿昏厥,真的是“开学雷击”,而且思考很久还是觉得无从下手,ddl 的逼近让我愈加焦躁......但是真的很崩溃、很绝望,“是我太菜了还是作业太难了?”我一遍遍地质问自己。后来又抓紧时间看了很多资料,和同学进行了交流,慢慢地才开始有了思路,然后开始 coding,最后终于在 ddl 之前完成了,而且还做了简易的测评机,让我顺利地通过了强测。除此之外,电梯单元、UML 单元,也都给了我不同程度的挑战,对我来说确实有一些煎熬。

“重生”体现在代码能力多方面大幅提高,学到了很多知识等。具体表现在以下方面:

  • 形成了良好的代码风格,如加空格、驼峰命名法、条件循环加大括号等
  • 培养了编写大型工程代码的能力。以前写的代码最多也就两百行(计算机组成写的 CPU 除外),但是现在能够写出两千行的代码了,而且可读性、可扩展性、健壮性都不错
  • 熟悉了 java 语言,能够运用 java 写出具有一定规模的代码
  • 培养了面向对象的编程思维,能够运用一些优秀的设计模式
  • 学习了多线程编程相关的知识
  • 学习了 JML 建模语言、UML 模型
  • 能够写出更加健壮的代码并进行较为充分的测试,培养了做测试的习惯
  • 学会了使用 Junit 进行单元测试
  • 提高了自己搭建测评机进行自动化测试的能力
  • 锻炼了自己的交流、分享能力和写博客的能力
  • 从吴际老师那里听了很多高端的东西,虽然有很多尚难以理解,但是相信未来会有用的
  • ......(太多了,写不下)

总而言之,OO 课程非常充实、有用,虽然学起来有点儿痛苦

给课程的三个改进意见

感觉 OO 课程已经非常完善了,课程体验非常棒,以下仅是从个人体验的角度提出的一些改进建议

  • 关于 pre:建议 pre 增加表达式解析和多线程相关的内容,让从 pre 到第一二单元的过渡更为平和
  • 关于互测:建议增加互测强度,让互测达到强测80%左右的强度,同时对于不合法数据,显示不合法原因。感觉第三单元的有些限制就很离谱,强测限制为100,互测限制为20,这差距有点儿大;互测时经常会出现数据不合法但是不知道为啥的问题,需要花大量时间排查,希望给几次显示非法原因的机会
  • 建议增加强测强度,增加测试点数量。强测挺强的,就是有点儿弱
posted @ 2022-06-26 21:59  xjh_buaa  阅读(17)  评论(1编辑  收藏  举报