面向对象程序设 一 第一次总结性Blog

一、前言

  回顾这三次题目集的学习历程,每一次都像是在编程世界里的一场闯关挑战,充满了收获与成长的感悟。
  第一次题目集的前 4 道基础题,主要聚焦于正则表达式的运用。刚开始做题时,我完全没留意到题目要求,自顾自地用常规方法编写代码,等反应过来才发现 “跑错赛道”,只好推倒重来。而最后一道电梯调度算法题(简化版 LOOK 算法),刚开始确实很难,拦了我很久。初次接触这类算法,不仅要理解其运行逻辑,还要对各种复杂情况进行精确判断。我耗费了大量时间编写代码,中途却发现算法思路存在根本性错误,重新梳理后,又因遗漏特殊情况导致程序漏洞频出,反复调试修改,才最终完成。
  第二次题目集包含两道基础题,核心目标是引导我们熟悉多类设计,理解各个类的功能及其相互关系,掌握单一职责原则(SRP)的实践方法,为后续电梯题目的复杂类结构设计打基础。在解题过程中,我花费了不少时间思考类与类之间的协作模式,以及如何合理分配功能。起初,我试图脱离老师的示例,凭借自己的想法设计类结构,结果在实际编码中遇到诸多问题,类与类之间的逻辑混乱、功能重叠,最后不得不回归示例,按照规范重新梳理,才真正领悟到多类设计的精髓。
  第三次题目集同样以两道基础题巩固类设计知识,相比第二次,这次的电梯题目在输入方式上有所变化。不过有了前两次的经验积累,我很快意识到只需对正则表达式稍作调整即可。在熟悉的类设计框架基础上,我结合新的输入要求,顺利且高效地完成了题目,明显感受到自己在编程能力和问题解决思路上的提升。

二、设计与分析

第一次电梯题目的设计与分析

题目要求: 设计一个电梯类,具体包含电梯的最大楼层数、最小楼层数(默认为1层)当前楼层、运行方向、运行状态,以及电梯内部乘客的请求队列和电梯外部楼层乘客的请求队列,其中,电梯外部请求队列需要区分上行和下行。

解决思路:用正则表达式处理输入数据,将输入数据分为一个电梯内队列,应该电梯外队列,然后用电梯调度算法(简化版LOOK算法)(每次到楼层或者准备出发前,电梯就会看看内部队列和外部两个队列(上行、下行)的 “排头” 请求。要是内部队列有更近的目标,就优先处理;要是外部队列的请求更合适,就调整方向过去。通过不断比较队列头的请求楼层,判断电梯该保持当前方向,还是得掉头,同时决定这层楼需不需要停稳开门接人),分别比较两个队列的头,判断不同情况的方向改变,以及是否应该打开该楼层

代码规模

第一次电梯代码规模如下图。

分析:  

  由于我将所有的功能全部写进了Elevator中导致Elevator 代码规模大、逻辑复杂、注释过少,维护难度高;Main 相对代码规模小、逻辑简单些,注释较合理,维护难度低一些。 后续可考虑对 Elevator 进行重构,拆分复杂方法、增加注释等提升代码质量。

类图设计:

第一次电梯的类图设计如下图。

类的具体设计

Main:   主类,从启动程序开始,负责接收用户输入的数据,再把这些数据按照规则读入并处理。同时,创建电梯对象、初始化相关参数

Directin:    是个枚举类型,专门用来定义电梯的运行状态,这样不仅代码看着一目了然,后续要是需要调整或扩展方向逻辑,改起来也方便

Elevator:   电梯类,完成Main中所需要的各种功能,Main类接收到的数据要怎么处理、电梯该怎么响应乘客请求,不管是计算楼层移动、管理内外请求队列,还是根据调度算法调整运行方向,这些功能都由这个类实现,设计的有问题,复杂度也很高

复杂度分析

第一次电梯的Source Monitor分析结果如下。

分析:

  • 从单个方法看,Elevator.processRequests()复杂度高达 62 ,语句数 127 ,最大深度 8 且调用数 83 ,是复杂度的 “重灾区” 。Main复杂度也达到 9 。这表明核心方法逻辑过于繁杂。主要原因在于类职责划分不够清晰,比如Elevator类承担了过多任务,像请求处理、运行逻辑等都集中于此,未将功能合理拆分到其他类中。大量方法相互调用、多层嵌套的条件判断和循环,使得代码逻辑纠缠不清,不仅理解困难,后续扩展新功能时极易引发连锁问题。
  • 反观部分方法如Elevator.addExternalRequests()等复杂度仅为 1 ,说明代码中方法复杂度差异悬殊,没有形成较为均衡的结构。这不利于代码的整体维护与优化,在排查问题时,难以预估不同方法的难度和工作量。
  • 此次借助工具分析代码,让我清晰认识到代码在类设计和方法实现上的不足。类的单一职责原则贯彻不到位,导致核心类臃肿不堪,方法复杂度失控。将所有的功能实现全部放在Elevator中,可读性很差,复杂度很高。

第二次电梯题目的设计与分析

题目要求: 对之前电梯调度程序进行迭代性设计,目的为解决电梯类职责过多的问题,类设计要求遵循单一职责原则(SRP),要求必须包含但不限于设计电梯类、乘客请求类、队列类以及控制类.相相对于上一题对于类的设计有了明确要求,必须拆分职责,遵循单一职责原则。

解决思路:设计电梯类(只专注处理电梯本身的动作,比如楼层升降、开关门这些基础操作)、乘客请求类(专门记录每个乘客从哪层出发、要到哪层,把请求细节单独管起来)、队列类(负责管理乘客请求的排队顺序,入队、出队、查看队列状态)以及控制类(根据电梯状态、请求队列情况,决定下一步该怎么调度电梯)的相应功能以及功能

代码规模

第二次电梯代码规模如下图。

分析

  总体而言,Controller和 Main代码复杂度较高,逻辑相对复杂且缺乏注释,维护难度大;而 Elevator、ExternalRequest、Requestqueue 代码相对简单,但均存在无注释问题。后续可考虑为这些文件添加注释,并对复杂度高的代码进行优化重构,提高代码的可读性和可维护性。

类图设计:

第二次电梯的类图设计如下图。

类的具体设计

Main:主要负责读取用户输入的数据。它需要解析输入格式,比如用正则表达式拆分每一条乘客请求,创建Elevator、RequestQueue等所需的类实例,同时,它还会调用其他类的方法,串联起数据处理和业务执行的流程。

Direction:枚举类用于定义电梯的运行方向,包含 “上行”“下行”“静止” 三种状态。程序中其他类在判断电梯移动方向、执行调度逻辑时,都会引用这个枚举类,保证方向定义的一致性和准确性。

State:枚举类用来记录电梯的运行状态,涵盖 “运行中”“停止且开门”“空闲等待” 等状态

Elevator:封装了电梯的核心属性与操作。属性方面,包含最大楼层数、最小楼层数、当前楼层、承载人数等基本信息;操作方法上,实现了楼层升降、开关门等功能。

ExternalRequest:专注于处理电梯外部的乘客请求,每个实例对应一个来自某楼层的上行或下行请求,包含请求楼层、请求方向等信息

RequestQueue:负责管理电梯内、外的乘客请求队列。它内部维护两个独立的队列,一个存储电梯内部乘客的目的楼层请求,另一个存储电梯外部的上行和下行请求。

Controller:是电梯调度逻辑的核心实现。它会实时获取Elevator类的当前状态和楼层信息,以及RequestQueue类中的所有请求数据,然后依据简化版 LOOK 算法,判断电梯的运行方向是否需要改变,决定在哪些楼层停靠并处理相应请求

复杂度分析

第二次电梯的Source Monitor分析结果如下。

分析: 

  • 与上次分析中Elevator.processRequests()复杂度极高类似,此次Controller.processRequests()复杂度为 13 ,语句数达 30 ,最大深度 5 ,调用数 27 ,也属于复杂度较高的方法。这表明在控制层处理请求的逻辑依旧复杂,可能存在大量条件判断、循环等操作。和之前Elecator类承担过多职责类似,这里Controller类在处理请求时没有将功能进一步细化拆分到其他类中,导致代码臃肿。
  • 像Contorller.determineDirection()复杂度为 12 ,语句数 17 ,最大深度 4 ,调用数 12 ,复杂度也不容小觑。这与之前Elector类中确定方向方法复杂度高相呼应,说明确定电梯运行方向的逻辑一直是代码中较为复杂的部分,可能是因为考虑的特殊情况较多,且没有进行合理的算法优化和逻辑梳理。
  • 不过,也有一些方法复杂度较低,如Controller.Controller(),Controller.getElevator()等,复杂度仅为 1 。这和上次分析中部分简单方法类似,说明代码中方法复杂度差异明显,分布不均衡。
  • 通过对比两次分析结果,发现代码在方法复杂度和结构上存在一些共性问题。一方面,部分核心方法复杂度始终居高不下,说明在类的职责划分以及算法设计上没有做到足够的优化和细化。在后续开发中,需要重新审视类与类之间的功能边界,将复杂功能进一步拆解,降低单个方法的复杂度。另一方面,代码结构可视化指标反映出代码的整体质量提升有限,无论是复杂度分布还是注释情况,都需要投入更多精力去改进。这提醒我在编写代码时,不能仅满足于功能实现,更要注重代码的可维护性和可读性,通过不断优化代码结构,遵循良好的编程规范,逐步打造高质量的程序。

第三次电梯题目的设计与分析

题目要求: 对之前电梯调度程序再次进行迭代性设计,加入乘客类(Passenger),取消乘客请求类,类设计要求遵循单一职责原则(SRP),要求必须包含但不限于设计电梯类、乘客类、队列类以及控制类:就是要加一个乘客类(Passenger),设计类的时候,还是得遵守单一职责原则。必须设计的类有电梯类、乘客类、队列类,还有控制类。和第二次题目比起来,就是用乘客类把 ExternalRequest 给替换掉

解决思路:新增一个 Passenger 类,用它来代表电梯里的乘客和在外面等着坐电梯的乘客。这个类能把乘客的各种信息和需求都装进去,这样不管是电梯内的队列,还是电梯外的队列,都能用这个 Passenger 类来处理

代码规模

第三次电梯代码规模如下图。

分析

  我延用的第二次题目集的类设计的,只是改了一下输入的格式,这个就类似于第二次的分析结果。

类图设计:

第三次电梯的类图设计如下图。

类的具体设计

Main:主要负责读取用户输入的数据。它需要解析输入格式,比如用正则表达式拆分每一条乘客请求,创建Elevator、RequestQueue等所需的类实例,同时,它还会调用其他类的方法,串联起数据处理和业务执行的流程

Direction:枚举类用于定义电梯的运行方向,包含 “上行”“下行”“静止” 三种状态。程序中其他类在判断电梯移动方向、执行调度逻辑时,都会引用这个枚举类,保证方向定义的一致性和准确性

State:枚举类用来记录电梯的运行状态,涵盖 “运行中”“停止且开门”“空闲等待” 等状态

Elevator:封装了电梯的核心属性与操作。属性方面,包含最大楼层数、最小楼层数、当前楼层、承载人数等基本信息;操作方法上,实现了楼层升降、开关门等功能

Passenger:这个类主要记录乘客输入的信息,比如乘客是从哪一层进入电梯的,要去的是哪一层,这些信息都由它来保管

RequestQueue:这个类里面有两个队列。一个队列专门记录电梯里面乘客要去的楼层信息,另一个队列则记录电梯外面等待的乘客的信息,通过这两个队列来管理不同位置的乘客请求

Controller:这个类是实现电梯运行算法的关键。它会根据电梯当前的状态、所在楼层,以及乘客的请求信息,运用电梯调度算法(简化版LOOK算法)来决定电梯接下来该怎么运行,是往上走还是往下走,在哪一层该停下来开门

复杂度分析

第三次电梯的Source Monitor分析结果如下。

分析: 

和上一次分析相比,Contorller.determineDirection()复杂度从 12 升至 17 ,语句数从 17 变为 20 ,最大深度从 4 增加到 6 ,调用数从 12 跃至 32 ;Controller.removeRequest()复杂度从 17 未变,但语句数从 24 增加到 33 ,最大深度从 8 降到 7 ,调用数从 24 大幅增至 67 。这表明这些关键方法的复杂度在持续上升或虽有波动但仍处于较高水平,说明在电梯运行方向判定、请求移除等核心逻辑上,代码没有得到有效简化,反而愈发复杂。而上次复杂度较高的Controller.processRequests(),这次复杂度从 13 降至 5 ,语句数从 30 减为 10 ,说明在处理请求这部分逻辑上可能进行了一定优化,但整体来看,Controller类中方法复杂度依然参差不齐。

三、踩坑心得

对类设计原则理解不足

  在做这几次题目集时,我深刻体会到类设计遵循单一设计原则的重要性。第一次写题目集,我一开始就没把握好这个原则,算法都错了还浑然不知,花了大量时间在错误的方向上。后续不断在细节上修改,过程痛苦不堪。这主要是因为没有提前规划好各个类的用途及关系,导致代码结构混乱,逻辑错误频出。不要像我一样将所有的功能全部加到Elevator类中。

类与类关系构建失误

  第二次题目集,我自己构思的类与类关系存在问题,陷入了找不出错误的尴尬境地。后来参考示例,却又没有合理运用方法,使得代码复杂度依旧居高不下。这说明我在理解和构建类之间的关系上存在欠缺,既不能独立设计出合理的关系,也不能很好地借鉴示例,导致代码质量不高。从我的复杂度分析中也可以看出我的类功能分配并不完美,在一些地方具有较高的复杂度。

缺乏创新与突破

  到了第三次题目集,本想重新梳理,挖掘之前遗漏的细节,让方法间联系更紧密。可实际操作时,遇到问题又找不出错误,最后只能沿用原来的思路,没有实现预期的提升。这反映出我在面对问题时缺乏创新思维和深入钻研的能力,不敢大胆突破原有思路,从而错失了优化代码的机会。
  你们写的时候可以参考我的踩坑心得,少一些痛苦。

四、改进建议

  • 提取公共代码

    检查Controller类中各方法,可以将重复代码片段,如在多个方法中都有对电梯状态或请求队列的类似处理逻辑,将这些公共代码提取出来,封装成独立方法。如此一来,减少代码冗余,也便于后续修改和扩展。
  • 重新审视类的职责

    目前Controller类中的某些方法承担了较多功能,导致部分方法复杂度高。可以重新梳理Controller类的职责,将一些功能合理拆分到其他方法中。我的Controller类中有很多示例有的方法我却找不到地方用,应该更细致的思考各个方法的联系,降低耦合度。
  • 增加注释

    鉴于代码注释量可能不足的情况,在关键方法、复杂逻辑以及类的定义处添加详细注释。对于Controller.processRequest()这类相对复杂的方法,注释应说明方法的整体功能、主要逻辑步骤以及参数和返回值的含义,方便自己和他人日后理解和维护代码。

五、总结

问题的理解和拆分能力得到提升

  从一开始面对复杂的电梯调度问题无从下手,到后来学会把大问题拆解成 “请求处理”“方向判断”“楼层移动” 等小模块,逐个击破。这种化繁为简的思维方式,让你在后续处理其他复杂任务时更有条理。

类的设计能力的提升

  初期因不懂 “单一职责原则”,把代码全塞在一个类里;到后来意识到类要各司其职,比如电梯类只负责电梯运行,队列类专门管理请求。这种对编程规范和设计模式的理解,是写好代码的基础。

心态与学习方法

  从一开始急于求成、盲目写代码,到后来明白要先规划再动手;从死磕问题到学会查阅资料、参考示例。这种心态和学习方法的转变,能让我在未来的学习中少走弯路。

建议

题目说明优化

在题目设置方面,建议将题目意图表达得更加清晰明确。以第一次迭代作业为例,算法相关内容在题目中说明模糊,致使众多同学理解偏差,耗费大量时间在错误方向上摸索。这种情况对同学们学习效率影响较大,即便从锻炼心态角度看,也不应在题目理解这类基础问题上让大家受挫。后续出题时,对于关键算法、规则等内容,应给予详尽、准确的阐述,避免因表述不清产生歧义,帮助同学们更高效地开展学习与实践。

学习引导强化

每次题目集完成后,可考虑提供示例代码,并明确指出其中涉及的知识点。通过展示规范、合理的代码结构与实现方式,同学们能更直观地学习借鉴,快速掌握正确的编程思路与技巧。同时,清晰罗列所需知识,能让大家明确学习方向,进行针对性复习与拓展,有助于更全面、系统地掌握相关知识体系,提升编程能力与综合素养。
 
 
posted @ 2025-04-16 19:41  落羽无痕  阅读(29)  评论(0)    收藏  举报