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
的建模 - 遍历一遍,将所有的
UmlRegion
与UmlStateMachine
绑定起来 - 遍历一遍,将所有的
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,这差距有点儿大;互测时经常会出现数据不合法但是不知道为啥的问题,需要花大量时间排查,希望给几次显示非法原因的机会
- 建议增加强测强度,增加测试点数量。
强测挺强的,就是有点儿弱
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· 10年+ .NET Coder 心语 ── 封装的思维:从隐藏、稳定开始理解其本质意义
· 地球OL攻略 —— 某应届生求职总结
· 提示词工程——AI应用必不可少的技术
· Open-Sora 2.0 重磅开源!
· 周边上新:园子的第一款马克杯温暖上架