面向对象——第四五六次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单一实体类演变为抽象基类具体子类的继承体系引入pinSignalsMapString, 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个parseMainv(G)22和runSimv(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.calcv(G)24、Main.evaluateDevicev(G)40、Main.parseMainv(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.calcv(G)24、Main.evaluateDevicev(G)40、Main.parseMainv(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等多个方法。建议引入工厂模式创建元件策略模式处理计算新增器件只需新增一个子类无需修改已有代码