当前位置: 首页 > news >正文

面向对象——第四五六次PTA作业集总结

第一次作业

第一次作业共包含三个核心类:Gate(门电路类)、Source(信号源类)、Main(主类)。
本次作业采用面向过程的数据结构单向信号传播机制,结构简单直白。其中Gate类为实体类,存储元件名称、类型、输入引脚数、编号、输出值以及各引脚的信号来源映射;Source类作为辅助记录类,用于存储信号来源的元件名和引脚号;Main类负责数据输入、元件创建、信号传播和结果输出。

类图

依赖关系:本次作业类之间依赖关系简单——Main类依赖Gate、Source以及集合容器、Gate类依赖Source作为引脚信号来源的映射值、Source类无任何依赖,属于独立辅助记录类。

复杂度分析

可以看到,复杂度怎么看都是有点爆的,这不仅是题目本身复杂性所致。
而且是代码规范性问题:
Main部分用大量 if-else 硬编码实现不同门电路(A/O/N/X/Y)的逻辑,每新增一种门类型都要修改这个方法,违反开闭原则。
每个分支里都重复了 “获取输入值→判空→计算结果” 的模板代码,冗余严重。
直接依赖 getVal 方法获取输入值,和外部状态耦合,难以单独测试。
用正则 + 多分支判断门电路类型,同时处理两种不同的命名格式(A(2)1 和 N1 等),逻辑复杂。包含大量格式校验和异常捕获,嵌套层级多,可读性差。

当然,也必然有可以优化之处:
如拆分main方法遵循, 单一职责重构 calc 方法;用策略模式消除 if-else;优化化 create 方法的逻辑等...
下为遵循单一职责示例:

查看代码

小结(一)
本次作业能拿100分是比较容易的,虽然main复杂度堆起来了,但大体无关紧要。
针对自定义对象存入哈希集合的需求,按照规范重写equals()与hashCode()方法,保证集合能够正确判断对象相等性,是面向对象开发中容器使用的重要实践。程序借助HashMap、List等集合类,统一管理大量逻辑门与输入信号,实现对象的批量组织与快速查找。
本次作业不仅完成了逻辑电路模拟的题目要求,也让我扎实掌握了类与对象、封装、集合框架、方法设计等核心知识点,学会用面向对象思维拆解实际问题、搭建程序结构。

第二次作业

第二次作业在第一次的基础上,新增了五类复杂器件:三态门(S)、译码器(M)、数据选择器(Z)、数据分配器(F),同时优化了信号传播机制。
本次作业共包含:Device抽象类及其五个子类(AndDevice、OrDevice、NotDevice、XorDevice、XnorDevice),以及专门处理复杂器件的独立类(TriDevice、DecDevice、MuxDevice、DemuxDevice)。此外引入了pinSignals全局信号池,实现引脚级别的信号存储与传播。

设计变化:

  • 从第一次的Gate单一实体类,演变为抽象基类+具体子类的继承体系

  • 引入pinSignals(Map<String, Integer>)作为全局信号池,替代了Gate.output的单一值存储

  • 信号传播从calc方法的递归求值,演变为propagateSignals的迭代传播机制

类图

依赖关系:Main类依赖Device体系及pinSignals信号池;Device为抽象基类,五个子类(AndDevice、OrDevice、NotDevice、XorDevice、XnorDevice)各自独立实现calculate方法;复杂器件(TriDevice、DecDevice、MuxDevice、DemuxDevice)的calculate返回null,其逻辑在Main.propagateSignals中硬编码处理,pinSignals作为全局信号池被Main和evaluateDevice共同依赖。本次作业类之间依赖关系适中。

复杂度分析

与第一次对比,平均圈复杂度从7.00降至5.58,得益于职责拆分更加细致,方法数从11增至26。但最大圈复杂度从28升至40,说明个别方法承担了过重的职责,成为新的复杂度热点。
第二次作业在复杂度管理上有明显优化:平均圈复杂度从10.18降至5.58,方法数从11增至26,职责拆分更加细致。但复杂器件(S/M/Z/F)未纳入继承体系,导致evaluateDevice方法成为新的"上帝方法",圈复杂度高达40,认知复杂度77,远超第一次的calc方法(v(G)=24)。
此外,printOutputs也因九种器件输出格式各异而达到v(G)=24。两个方法合计贡献了64的圈复杂度,占全文件145的44%。如果继续沿用这种设计,第三次作业新增子电路功能时,这两个方法将进一步膨胀,最终导致架构失控。根本解决方案是将S/M/Z/F也纳入Device继承体系,各子类独立实现calculate和output方法,消除硬编码分支。

小结(二)

通过本次作业,我加深了对继承与多态的理解。五种基础门通过继承Device分别实现calculate方法,代码结构比第一次更加清晰。pinSignals全局信号池的引入也有效解决了多引脚器件的信号存储与读取问题。
优化方向可以将S/M/Z/F也纳入Device继承体系,各子类独立实现calculate和output方法,消除evaluateDevice和printOutputs中的硬编码分支。同时废弃Gate体系,统一信号存储为pinSignals单一信号池,避免双重路径问题。

第三次作业

第三次作业在前两次的基础上,新增了两大核心功能:子电路(模块化)异常输入检测
本次作业共包含:BlockModule(模块类)、LogicUnit抽象类及其五个子类(AndUnit、OrUnit、NotUnit、XorUnit、XnorUnit)、WireLink(线网类),以及Main类中大量的静态解析方法。此外,第一次的Gate体系、第二次的Device体系与第三次新增的LogicUnit体系三套元件表示同时共存。
类图

依赖关系:Main类依赖BlockModule、LogicUnit、WireLink以及前两次遗留的Gate/Device体系;BlockModule包含members(模块内元件映射)和internalLinks(内部线网列表),递归解析子电路定义;LogicUnit为抽象基类,五个子类各自实现compute方法;WireLink作为独立线网记录类,存储驱动端和接收端信息。本次作业类之间依赖关系急剧增加,且三套元件体系功能重叠,导致架构混乱。
复杂度分析

第三次作业最大圈复杂度虽从40降至22,但复杂方法数量从4个增至5个,parseMain(v(G)=22)和runSim(v(G)=19)成为新的复杂度热点。runSim的认知复杂度高达81,是全文件最难理解的方法。根本问题在于三套元件体系(Gate/Device/LogicUnit)共存,导致信号存储存在三重路径,子电路克隆时内部映射丢失。parseMain承担了四个独立职责,严重违反单一职责原则。有时候做题就是图方便难免违反。
改进思路:废弃Gate和Device体系,统一为LogicUnit一套体系;子电路采用命名空间重命名而非克隆;将parseMain拆分为解析、创建、连接、验证四个独立方法。
下为删除 cloneUnit 方法 + 改造 registerUnitByPin

查看代码


小结(三)

通过本次作业,我对模块化设计有了更深刻的理解,第三次作业也是这三次中最重量级也最重要的一个。子电路的本质不是简单的类嵌套,而是命名空间隔离——将一组元件封装为独立单元,对外只暴露输入输出端口,内部信号对外部不可见。这让我联想到操作系统中的进程隔离概念,虽然层级不同,但思想相通。异常检测机制的引入也让我认识到,在复杂系统中输入验证不是可有可无的附属品,而是保证系统健壮性的第一道防线,一个设计良好的系统应该在错误发生时给出清晰的提示,而非静默失败。
本次作业已完成大部分测试点,能想到的输入基本测试过无问题,但仍没有满分,还是有细节待究。

采坑心得

第一次作业踩坑记录

坑一:忽略引脚0的限制

第一次作业中,getVal方法没有s.pin == 0判断,导致读取信号时可能取到输入引脚而非输出引脚。
当上层元件连接到X1-1(异或门的输入引脚)时,getVal会返回g.output,但此时输出引脚还没计算完,或者返回的是错误的信号值。

查看代码

第二次作业踩坑记录

坑一:第一次的Gate.output和第二次的pinSignals同时存在,读取和写入走不同路径。

可以废弃Gate.output,统一从pinSignals读取:

查看代码

坑二:evaluateDevice中过早返回

evaluateDevice在检查控制引脚时,一个引脚缺失就直接return false,但其他引脚可能还没传播到。
控制引脚和数据引脚同时传播时,数据先到控制后到,由于控制缺失直接返回false,导致同一轮次内无法完成求值。
应该所有引脚读取完再统一判断:

查看代码

第三次作业踩坑记录

坑一:第一次的Gate、第二次的Device、第三次的LogicUnit同时存在,一个元件可能出现在三个Map里。

registerUnitByPin遇到子电路引脚时,先克隆LogicUnit,但runSim中又从Device体系读取信号,导致信号断裂。
可以废弃前两套体系,只用LogicUnit一套。

查看代码

坑二:异常检测过于激进

某些正常连接格式(如多个输出引脚连接到同一个输入引脚?题目说一个输入引脚不能连接多个输出引脚,但反过来是可以的)被误判为错误。->原本正确的Case因为异常检测误判而输出错误信息。

要仔细对照题目要求,只检测明确禁止的情况(没办法只能猜,猜也猜不到满分😀)

查看代码

踩坑小结

  • 能跑通的代码不一定对,引脚0的坑就是血的教训

  • 信号源只能有一处,多路径必然混乱

  • 不要打补丁,要重构——欠下的技术债连本带利都要还
  • 以样例为准,题目描述与样例冲突时信样例

  • 一个方法只做一件事,parseMain和calc就是反面教材

(PS.最大的坑是无法理解题目要求。在第一次数字电路作业中,题目看似写的规则十分清晰,实则暗藏玄机,示例中的OUT是什么?0是哪里来的?原来OUT不是很有所谓,0是默认👴等等,全是看示例自己猜,自己摸索,自己去想,自己去试试是不是这样的,后面的一些测试点更是这样,找极端逻辑极端示例,本次作业比上三次作业最强悍之处在于测试点更为阴间,能想到的点都能过,也许是一些答案与出题者想的有所出入?本次作业想做到全对花费的时间无疑是巨大的,我也没有做到满分的地步,做到高分应该算是很好的了)

改进建议

一、代码结构改进

问题:三次作业的方法圈复杂度持续超标,Main.calc(v(G)=24)、Main.evaluateDevice(v(G)=40)、Main.parseMain(v(G)=22)都是典型的"上帝方法"。
建议:严格遵循单一职责原则,每个方法只做一件事。以parseMain为例,拆分为parseInputLines、parseConnections、validateConnections、buildNetwork四个方法,各自独立测试。
问题:三套元件体系(Gate/Device/LogicUnit)共存,信号存储存在多重路径。
建议:只保留一套元件体系,统一信号源。若从第三次作业重构,应废弃Gate和Device,只保留LogicUnit,所有信号通过唯一的sigMap存取。

二、架构设计改进

问题:子电路采用克隆物理元件的方式实现,导致内部信号映射丢失。
建议:采用命名空间重命名。子电路实例化时,给其内部所有信号名加上前缀(如C1.And1-0),信号池统一维护,无需克隆。
问题:复杂器件(S/M/Z/F)未纳入继承体系,在evaluateDevice中硬编码处理。
建议:将S/M/Z/F也纳入Device继承体系,各子类独立实现calculate和output方法,消除硬编码分支。

三、可扩展性改进

问题:新增器件类型需要修改create、calc、evaluateDevice、printOutputs等多个方法。
建议:引入工厂模式创建元件,策略模式处理计算,新增器件只需新增一个子类,无需修改已有代码。
问题:引脚号计算分散在各处(数据选择器输出引脚、译码器输出起始引脚等)。
建议:在Device基类中定义getOutputPin()和getInputPins()等方法,由各子类自行计算,外部只需调用接口。

四、异常处理改进

问题:异常检测逻辑与解析逻辑混在parseMain中,圈复杂度22。

建议:独立的InputValidator类负责所有异常检测,解析完成后统一校验,职责清晰。

问题:错误信息不够明确,难以定位问题。

建议:异常发生时输出具体的错误位置和期望格式,如ERROR: Pin X1-1 not connected,而非笼统的ERROR: invalid input。

总结

三次迭代作业做下来,最大的感受就是:能跑和好改是两码事。

一、代码结构改进
问题:三次作业的方法圈复杂度持续超标,Main.calc(v(G)=24)、Main.evaluateDevice(v(G)=40)、Main.parseMain(v(G)=22)都是典型的"上帝方法"。

建议:严格遵循单一职责原则,每个方法只做一件事。以parseMain为例,拆分为parseInputLines、parseConnections、validateConnections、buildNetwork四个方法,各自独立测试。

问题:三套元件体系(Gate/Device/LogicUnit)共存,信号存储存在多重路径。

建议:只保留一套元件体系,统一信号源。若从第三次作业重构,应废弃Gate和Device,只保留LogicUnit,所有信号通过唯一的sigMap存取。

二、架构设计改进
问题:子电路采用克隆物理元件的方式实现,导致内部信号映射丢失。

建议:采用命名空间重命名。子电路实例化时,给其内部所有信号名加上前缀(如C1.And1-0),信号池统一维护,无需克隆。

问题:复杂器件(S/M/Z/F)未纳入继承体系,在evaluateDevice中硬编码处理。

建议:将S/M/Z/F也纳入Device继承体系,各子类独立实现calculate和output方法,消除硬编码分支。

三、可扩展性改进
问题:新增器件类型需要修改create、calc、evaluateDevice、printOutputs等多个方法。

建议:引入工厂模式创建元件,策略模式处理计算,新增器件只需新增一个子类,无需修改已有代码

http://www.jsqmd.com/news/1113079/

相关文章:

  • SAP-ABAP:SAP QM 检验结果录入核心利器:BAPI_INSPOPER_RECORDRESULTS 完全指南
  • ChatGPT做PPT内容的黑箱真相:我们逆向拆解OpenAI官方提示链,还原高通过率大纲的7层嵌套指令结构
  • 如何利用软件计算流域面积(Global Mapeer)
  • CUUG-AI时代数据库认证培训的价值
  • 内存价格凶猛上涨!三大原厂扩产遇阻,苹果难逃存储荒反噬
  • 焊接符号问答大全
  • AI辅助毕业设计:3步法提升开发效率与创新
  • 机器学习面试数据准备20问:从清洗到归因的工程实战指南
  • 为什么说“无需逐字雕琢”也能搞定朱雀 AI 判定?
  • TTS-Backup完整指南:5步轻松保护你的桌游模拟器珍贵数据
  • Gemini 3.1 Pro与GPT-5.4工程选型指南:认知中枢vs执行引擎
  • 从沈管家看AI数字员工的技术演进:告别“聊天”,走向“执行”
  • 3分钟掌握RSA攻击神器:RsaCtfTool实战指南
  • 梯度下降实操指南:从原理到工业级调参避坑
  • 档案管理系统怎么选不踩坑?这6个功能少一个都不行
  • 微信聊天记录备份:3种导出格式与年度报告生成指南
  • OpenClaw部署安装常见问题汇总与解决方法
  • 如何在Obsidian笔记中直接运行Python代码:Obsidian Jupyter插件完整指南
  • 国内主流企业级大模型运营治理平台横向排行
  • 海关政策法规查询进入大模型时代:监管要求、公告文件与业务规则如何智能问答
  • 计算机毕业设计之基于Java Web的医护系统的设计与实现
  • 3400万罚单惊醒“装睡”的企业:合规,从来不是选择题
  • 不造假也会被撤稿?临床科研自查盲区很多人忽略
  • Kolmogorov-Arnold网络:极简可控建模的工程实践指南
  • 实习第二天,反反复复敲“linux/QNX操作命令”
  • OpenAI旗舰编程工具Codex一年写640TB烧穿硬盘,日志问题频发修复仍留隐患
  • 最新量化入门,概念代码回测模拟都要对上工具
  • 终端clear命令失效
  • JMeter测试SOAP接口全攻略:从WSDL解析到性能压测
  • AI的技术栈全知道