Blog 1:题目集05-07分析
Blog 1
前言
题目集5-7题目量较大,对于学习面向对象程序设计的新手来说较为困难,但一些难度较大的题目老师都附有类图设计供我们参考,这大大削减了题目难度。这几次题目集知识面涵盖广泛,涉及正则表达式(校验QQ号,验证码校验等题)、类与对象方面知识,要求我们掌握定义私有属性及相关方法,深度考查对类的封装、属性与方法的定义以及面向对象编程思想的运用。此外,还结合生活实际,锻炼了我们复杂业务逻辑处理能力(步枪,雨刷等题)。不仅如此,此次题目集在算法逻辑和计算思维上深入考察,如“电梯难题”需综合考虑电梯的各种属性如最大最小楼层、运行状态,以及请求队列管理和调度算法等,又如“蒙特卡洛方法求圆周率”涉及特定算法的应用和数值处理。这几次题目集对综合编程素养和逻辑思维能力有着较高要求。
设计与分析
点与线(类设计)
该题的源码整体结构清晰,符合面向对象设计。Point 类表示二维平面上的点,包含 x 和 y 坐标,有构造方法、属性的获取和设置方法,以及显示坐标的方法。Line 类表示线,由两个 Point 对象和颜色属性组成,能计算线的长度并显示线的相关信息。Main 类负责接收用户输入的点坐标和线的颜色,对输入进行格式校验,若格式正确则创建 Point 和 Line 对象并显示线的信息,否则输出错误提示。Point和Line类封装良好,分别承担点和线的属性与操作,Main类负责输入处理和业务流程控制,实现了点和线的基本功能,如信息展示、线段长度计算,还具备输入格式校验。不过,代码存在一些不足。异常处理过于宽泛,捕获Exception虽能避免程序崩溃,但难以定位具体问题,给调试带来困难。部分代码存在重复,未提取成独立方法,可维护性有待提高。


汽车挡风玻璃雨刷问题(类设计)
这题的源代码定义了四个类:Lever 表示控制杆,有上下移动位置的方法;Dial 表示刻度盘,可调节刻度;Brush 表示雨刮器,能设置和获取速度;Agent 作为协调者,根据控制杆位置和刻度盘刻度计算雨刮器速度。Main 类是程序入口,接收用户输入指令,根据指令改变控制杆和刻度盘状态,调用 Agent 计算并显示雨刮器速度。在结构和逻辑上有可圈可点之处,但也存在不足。结构方面,类的划分较为清晰,Lever、Dial、Brush各司其职,Agent负责统筹协调,Main处理输入,功能模块明确。逻辑上,各方法实现基本符合业务需求。然而,dealSpeed中多层switch嵌套使代码复杂度上升,可读性欠佳;并且未对输入异常(如非数字输入)做处理,程序健壮性不足。


销售步枪问题
这题源码设计了四个类。lock、stock、barrel 类分别表示步枪的机、枪托和枪管,有私有属性 price 表示价格,通过构造方法初始化价格,并提供获取价格的方法。salesOrder 类记录三种部件的销售数量,提供获取数量、计算销售总额和佣金的方法。在 Main 类中,通过 Scanner 获取用户输入的三种部件销售数量,创建相关对象。判断数量是否在规定范围内,若符合则计算销售总额和佣金并输出,否则输出错误提示。结构相对清晰,但也存在不足。从优点看,类划分明确,符合面向对象思想,且业务逻辑实现完整,能准确计算销售总额与佣金。然而它也存在缺陷,类名未遵循首字母大写的 Java 命名规范;未处理输入非数字等非法数据情况,因而健壮性不够。


蒙特卡洛法求圆周率
该题的源码结构比较完整。coordinate 类表示坐标,存储横纵坐标;rectangle 类描述矩形,包含宽、长和矩形内一点坐标;circle 类表示圆,有半径和圆心坐标;Msimulation 类整合矩形和圆,实现矩形格式验证、蒙特卡洛模拟及距离计算等功能。在 Main 类中获取用户输入,创建相关对象,调用 Msimulation 类方法进行判断和计算,根据结果输出相应信息。通过定义 coordinate、rectangle、circle 和 Msimulation 类,将坐标、矩形、圆形以及蒙特卡洛模拟功能分别封装,层次分明,便于理解和维护。功能实现相对完整,实现了使用蒙特卡洛方法求圆周率的核心逻辑,包括随机投点判断是否在圆内,以及根据投点结果计算圆周率近似值,并进行结果判断输出。但它也有明显缺陷。类名命名不大规范,影响代码规范性;异常处理不完善,对输入非法字符等情况无应对措施,易导致程序崩溃;部分方法命名表意不准确,易造成误解。在编写的过程中,我对类的封装和方法设计有了更深入理解,体会到合理的代码结构能使复杂功能更易实现和维护。同时我也意识到代码规范、异常处理等方面的重要性,后续需加强代码质量把控,注重细节以提升程序健壮性和可读性。


单部电梯调度程序
该题的第一次作业源码仅分成 3 个文件。从图中的分析数据来看,平均每个类方法数 2.33,平均方法语句数 16.29 ,最大复杂度和深度达 43 ,平均复杂度 10.09 ,代码逻辑复杂,维护困难。该源码设计了三个类:elevatorFundamental 类用于表示电梯的基本功能,包含电梯楼层范围、当前楼层、运行方向、状态以及内外部请求列表等属性,还有添加请求和控制电梯运行的方法。Request 类表示电梯的请求,包含请求楼层和方向。Main 类是程序入口,负责获取用户输入并调用电梯类的方法。通过类的划分,将电梯的不同功能模块进行封装,便于理解和维护。实现了电梯处理内外部请求、确定运行方向、停靠等基本功能。elevatorFundamental.java:是主要代码文件,有 148 行。语句数 110 条,分支数 36.4 ,调用数 51 次,显示其逻辑分支与操作调用较多。拥有 1 个类且含 5 个方法,平均每个方法 19 条语句,最大复杂度和深度均为 43 ,说明存在复杂难理解的方法,不利于后期修改与扩展。elevatorTest.java:共 32 行,含 21 条语句,分支数 23.8 。有 1 个类、1 个方法,最大复杂度为 6 ,深度 6 ,整体逻辑相对简单,注释占比 11.1% ,在几个文件里注释情况稍好。Test.java:仅 9 行,5 条语句,无分支和调用。有 1 个类、1 个方法,最大复杂度和深度都为 1 ,是三个文件中最简单的,注释占比同样为 11.1% 。经分析之后,源码的缺陷很大,不足之处较多。move 方法代码冗长,逻辑复杂,可进一步拆分细化,同时处理方向确定、楼层移动、停靠判断等多项逻辑,致使代码错综赘余。确定方向的逻辑重复出现,且在判断是否停靠时,多种条件嵌套,可读性差。应厘清逻辑尽量拆分出独立方法,使各功能职责单一,提升可维护性。另外,算法逻辑不够清晰,处理请求优先级时较简单,未考虑复杂场景,如多个同向外部请求的先后顺序;当内外部请求混杂时,可能出现资源浪费或响应不及时情况,没有对请求进行更合理的调度和排序。在思考此电题算法时,困难太多,挑战太大。需综合处理内外部请求,规划运行方向,在复杂请求场景下,要细致考量各因素间的逻辑关系。在处理过程中,理清逻辑、优化算法存在非常高的难度。建议重构复杂方法降低复杂度,同时进行代码审查,排查统计异常及不合理结构,以优化代码质量。


第二次作业在第一次作业的源码的基础之上进行迭代优化,有了较大的改进。整体而言,分支占比 23.7%,调用数 75 次,说明方法间调用较为频繁。有 3 个类,平均每个类方法数达 7.67 个,但平均每个方法语句数仅 4.26 条,最大复杂度为 1,整体方法复杂度低,逻辑嵌套深度也较浅。定义了 Direction 和 State 枚举类,分别表示电梯的运行方向和状态。ExternalRequest 类用于表示电梯外部的乘客请求,包含请求楼层和方向。Elevator 类包含电梯的当前楼层、运行方向、状态、最大楼层和最小楼层等属性,以及相关的访问和设置方法。RequestQueue 类用于管理电梯的内部和外部请求队列,提供添加请求和获取请求队列的方法。Controller 类负责电梯的调度过程,包括确定运行方向、移动电梯、判断是否停靠、开门和移除已处理的请求等功能。Main 类从控制台读取输入,创建电梯、请求队列和控制器对象,并将请求添加到队列中,Controller类在项目中较为特殊,有 134 行,82 条语句,分支占比 30.5% 高于整体水平,调用数达 69 次,足见其在项目中的活跃程度。但注释占比仅 0.7% ,理解难度大。最后调用控制器的 processRequests 方法来模拟电梯的运行过程。遵循了单一职责原则,将不同功能封装在不同类中,Elevator 负责电梯状态管理,RequestQueue 管理请求队列,Controller 进行调度,代码结构清晰,可维护性强。并且合理使用了枚举,使用 Direction 和 State 枚举类,使代码更具可读性和可维护性。与第一次作业仅设计了elevatorfundamental类相比,代码结构更合理,可维护性和可扩展性大大增强。并且合理使用了枚举类,使代码语义更加明确,代码逻辑更加清晰,更易于理解,相较于第一次作业的源代码,规范性与实用性大大提高。但仍然有缺陷,当务之急是增加代码注释,尤其是关键逻辑和方法功能处。特别对于Controller类,因其调用频繁且分支较多,可考虑优化结构、提炼方法,进一步降低复杂度,提升代码的可维护性与可扩展性。


第三次作业对之前电梯调度程序再次进行迭代性设计,涵盖 7 个文件,代码分支占比 22.4%,意味着代码中条件判断逻辑有一定占比。调用数多达 101 次,表明方法间交互频繁。项目中有 8个类,平均每个类拥有 10 个方法,平均每个方法含 4.5 条语句,最大复杂度为 1 ,最大深度为 4 ,平均深度 1.48 ,平均复杂度 1.00 ,与前一次源码相比,保持了代码整体较低的复杂度,逻辑嵌套依旧程度不深。加入乘客类(Passenger),取消乘客请求类,类设计要求遵循单一职责原则(SRP)。新定义的Passenger类用于表示乘客请求信息,包含private Integer sourceFloor(表示乘客发起请求所在的楼层)和private Integer destinationFloor(表示乘客想要到达的楼层)两个属性,以及public Passenger(Integer sourceFloor, Integer destinationFloor)(用于创建外部请求的乘客对象)和public Passenger(Integer destinationFloor)(用于创建内部请求的乘客对象)两个构造方法,还囊括获取和设置乘客的出发楼层、目的楼层的方法,将乘客的请求信息进行了有效的封装和管理,为电梯调度程序的逻辑实现提供了基础的数据支持。第三次作业的源码与前两次相比职责更加清晰,严格遵循了单一职责原则,将各功能完备地封装在对应类中。代码结构更加清晰,可读性和可维护性都得到提高,并且可扩展性增强。编写过程中,明显感觉难度提升。新增的乘客请求格式和处理规则,使逻辑判断更为复杂,处理请求时,需更细致考虑各种情况。需要理清思路,合理进行类的设计以及职责划分,我深刻体会到良好的代码结构在应对复杂需求时的重要性,它不仅能降低开发难度,还为后续维护和扩展奠定坚实基础。针对Controller,还可以进行优化,因其分支多、调用频繁,可继续通过模块化拆分、抽取子方法等手段,降低代码耦合度与复杂度,从而增强代码的可读性与可维护性,为项目后续迭代和拓展奠定良好基础。


踩坑心得
这几次的题目集题目需求复杂,对思维严谨性和逻辑思考能力提出极高要求。编程时,潜在错误隐患众多,每一步推进都要反复斟酌,遇到了许多困难,犯了许多错,踩了许多坑。接下来我来总结一下遇到的问题。
数据格式控制出错
使用printf输入两个浮点型数据时,我需要控制输出数据为两位小数,错将%.2f打成了%.2d,导致输出结果混乱。虽然是比较小的错误,但是这一错误暴露出我在基础语法掌握上的欠缺与编程时的疏忽。反映出我对数据类型与对应输出格式的关联理解不深,未养成严谨的编程习惯。以后需要注意,不要再犯这种低级错误。
在步枪交易作业中,定义了佣金和销售额两个double类型的变量,但输出语句中使用%.2d输出。


算法逻辑错误
如在蒙特卡洛法求圆周率时,圆心的计算公式错误了。测试数据时发现无论怎么输入结果都是failed。添加了输出圆心坐标的调试语句后发现:这里半径为0.5,圆心应当在(0.5,0.5)而调试结果为(-0.5,-0.5)。查找原因发现圆心的计算公式写错了,这里的减号应为加号。


修改之后,结果就没错了。

此问题也相对低级,以后编程时许多注意思考,避免算法逻辑上的漏洞与错误。
题目理解不清晰
在做汽车雨刷题时,没有明确题目要求,没有确切理解题目的意思,导致输出结果与测试样例参考结果有出入。经过仔细读题与检查,发现我没有将雨刷初始状态的速度置为0,导致输出结果不符合预期。

经过调试与修改后获得正确结果。

这虽然是个小小的审题问题,但足够说明我对题目所描述的系统行为缺乏深入分析,没有准确梳理出系统各个部分的初始状态、变化规则等关键要素。未能充分认识到雨刷初始速度这一状态对整个系统输出结果的影响,说明在需求分析阶段没有全面考虑问题,没有建立起完整准确的系统模型。下次面对题目的需求进行分析时,需要尽量做到细致、缜密、全面与周到。
输入格式考虑不全
在编写电梯程序时,程序未能成功读取测试数据输入的”end”和”END”。我只考虑到小写情况,未能考虑到大写情况,导致输入”END”时,输入陷入死循环不会结束。

修改之后可以成功输出正确结果。


索引越界异常
同样是电梯程序里发生的错误,当我将已完成的请求清除出队列时,在代码中调用了 ArrayList 的 remove 方法,并且尝试移除索引为 0 的元素,但此时这个 ArrayList 中并没有任何元素(长度为 0),因而报错。

调试方法是需要我们在访问集合元素之前,先检查集合是否为空,或者确保集合中已经有足够的元素存在。在调用 remove 方法之前,可以添加一个判断:

输出结果死循环
电梯程序里当我进行测试数据时,会一直循环输出非法楼层楼层数据,不会停止。如图所示,电梯一直在一楼与二楼间反复运行,陷入死循环。

这是逻辑判断出现了问题,程序没有正确设定或更新目标楼层,没有根据请求合理设置目标楼层,导致电梯始终在这两个楼层间往复,因为它没接收到前往其他楼层的指令。在判断是否切换方向时,对于 inlist 和 outuplist、outdownlist 的综合判断逻辑不够清晰,导致电梯可能频繁不必要地切换方向,出现类似在两个楼层间来回振荡的情况
改进建议
销售步枪问题:当前代码缺乏输入验证和异常处理机制。首先,可以在构造函数里添加对输入价格的验证,确保其为非负数,避免出现不合理的价格输入,当输入的价格为负数时,输出”Invalid Format ”使程序能更明确地处理错误情况。其次,可以增加 setPrice 方法,允许在对象创建后修改价格,提高类的灵活性,并且在该方法中同样添加输入验证和异常处理。另外,可以适当修改方法名称,遵循 Java 的命名规范,增强代码的可读性。
蒙特卡洛法求圆周率:可以修改部分变量的命名,使部分变量命名更加准确,更具描述性,可将 “count” 可改为 “pointsInsideCircleCount”。而且可以添加输入验证,可以在“simulation” 方法中,对输入的 “n” 进行验证,确保其为正整数。此外,可以优化代码性能,减少重复计算,如在getDistance方法中,可提前计算(dot1.getX()-center.getX())和 (dot1.getY()-center.getY())的值,减少重复计算。
单部电梯调度程序:当前代码仅对输入的楼层进行简单检查,可以添加更详细的输入验证,如检查输入是否为合法的整数、范围是否合理等。以及在读取输入和解析输入时,添加异常处理机制,避免因输入错误导致程序崩溃添加对电梯异常情况的处理,如楼层越界、请求队列异常等。然后可以强化模块化设计,将输入处理、电梯初始化、请求处理等逻辑拆分成独立的方法,提高代码的可读性和可维护性。并且功能可以更加完善,添加方法来判断乘客请求的方向(向上或向下),方便后续电梯调度判断。以及添加请求优先级判断,可添加优先级属性,支持不同优先级的请求处理。还可以添加方法返回方向的描述信息,方便记录和调试。还可以进行算法的改进与优化,改进调度算法,优化 determineDirection 方法,考虑更多因素来确定电梯方向,如当前电梯的运动状态、最近的请求楼层等。优化楼层处理,优化 move 方法,避免无限循环的潜在风险,添加楼层边界检查。以及前面提到的controller类还可以持续迭代优化,降低耦合度,对代码结构进行优化,拆分复杂逻辑,提炼出独立方法,以此降低复杂度,提升代码的可维护性与可扩展性。
总结
学习心得
这三次的习题集针对特别难!特别是电梯难题,写的时候花了很多时间,耗费了许多精力,殚精竭虑绞尽脑汁但最后也没过测试点,写得很绝望很无助很崩溃,一度有过放弃学习自暴自弃的想法,但好在我没有放弃,尽管很难,但我仍在坚持。经过了本阶段三次题目集的学习与实践,我收获了多方面的知识与技能,同时也明确了后续需要进一步钻研的方向。经过三次题目集的磨练,我对 Java 编程的理解更为深入。学会了类与对象的构建、封装和交互,懂得如何通过类来组织数据和行为,以解决特定的业务问题。熟练掌握了方法的定义、参数传递和返回值处理,能运用方法实现各种功能逻辑。而且,我也能熟练地用循环和判断语句来控制程序的走向,让程序按部就班地运行。不过,我也发现自己有很多不足的地方。有时候输入一些奇怪的数据,程序就崩溃了,要不然就进入死循环,这让我很头疼。所以之后我要好好学习怎么处理异常情况以及控制输入输出格式,让程序更稳定。而且算法的优化能力有待提高,编写的代码在处理大规模数据或复杂逻辑时,效率可能较低,需要学习更多知识,优化代码性能。此外,设计模式的应用也需要深入研究,以提高代码的可维护性、可扩展性和复用性。
意见
希望老师在课堂上可以再多讲讲类的设计与类图的画法。目前对这部分内容的理解还不够深入,多一些讲解和实例分析,能帮助我们更好地掌握相关知识,提升编程能力。另外,希望老师可以安排专门的答疑时间来指导我们。在学习中总会遇到各种各样的问题,有专门的答疑时间,让我们可以自愿参与答疑,我们就能及时解决疑惑,跟上学习进度。当作业里遇到复杂算法的难题时,也希望老师能多多提点我们,把题目的逻辑讲得更清晰一些,这样我们就能更轻松地理解和掌握复杂的算法知识。
24201637-张睿

浙公网安备 33010602011771号