一、前言:
本学期《面向对象程序设计》课程的后三次作业(作业集4~6),围绕“数字电路模拟系统”展开了一次完整的迭代开发。从最初的基础逻辑门电路模拟,到引入三态门与译码器等复杂元件,再到最终的数据选择器、数据分配器及多层级联电路支持,系统功能逐步完善,编程难度逐级提升。
三次作业的知识点覆盖了:抽象类与继承、多态机制、Map集合使用、枚举类型、迭代算法设计、位运算、工厂模式、信号传播机制、组合与聚合关系等核心内容。
题量方面:第一次作业(作业集4)约300行代码,第二次(作业集5)约550行,第三次(作业集6)约700行,呈梯度增长。难度上,第一次侧重基础元件建模与单轮信号传播;第二次增加了控制引脚概念和多输出元件;第三次引入了事件驱动信号传播和复杂级联电路处理。总体来说,难度递进合理,但第三次作业中信号传播算法的坑点较多,需要格外注意。
1.知识点总结:
(1)作业集4:基础面向对象编程(继承、多态)、简单门电路模拟(与门、或门、非门、异或门、同或门)
(2)作业集5:复杂元件建模(三态门、译码器)、控制引脚概念、多输出元件处理
(3)作业集6:数据选择器与分配器、信号传播机制、复杂电路级联、迭代算法优化
2.题量与难度:
(1)每次作业集包含1道核心编程题,但功能要求逐步递增
(2)从基础5种门电路到9种元件,从单一输出到多输出引脚
(3)难度呈螺旋式上升:数据结构设计 → 控制逻辑 → 信号传播算法
(4)总结来说就是非常难,题目本身就不易读懂,其中逻辑关系还得自己去查明白,所消耗的时间和精力也更多。
二、设计与分析:
2.1数字电路模拟程序-1:基础门电路模拟
(1)作业需求: 实现与门、或门、非门、异或门、同或门五种基本门电路,支持多输入引脚,根据输入信号计算输出。
(2)类图:

(3)BUG分析:
1.数据分配器选择逻辑错误:控制端引脚顺序理解错误。题目图中的 AB 组合:00→W0,01→W1,10→W2,11→W3,说明 A 是高位,B 是低位。但我的测试用例 A=1,B=0,C=0 却选择了 W1,说明实际测试中引脚0是最低位。
2.数据选择器选择逻辑错误:引脚出现问题,S0应该是最低位,但我的代码里面正好写反了。
3.信号传播需要多轮迭代:我的代码里面一开始并没有进行多轮迭代,解决方法可以使用while(changed)循环,每轮清空输入重新设置,直到没有新输出产生。
(4)心得:第一次作业让我理解了抽象类在多态中的应用,以及如何通过继承实现代码复用。使用 Map<Integer, Integer> 存储引脚输入值,既支持可变数量的引脚,又便于快速查找。
2.2数字电路模拟程序-2:引入三态门(控制引脚+输入引脚+输出引脚)和译码器(控制引脚+输入引脚+多个输出引脚),支持控制引脚逻辑。
(1)作业需求:第二次作业在第一次基础上增加了三个新元件类
(2)类图:

(3)Bug分析:
1.数据分配器输出引脚顺序错误:数据分配器的控制引脚位序理解错误,导致选中的输出通道不正确。误认为控制引脚0是最高位(MSB),使用 1 << (ctrlCount - 1 - i) 计算。实际引脚0是最低位。
2.复杂电路级联传播失败:在多层级联电路中(如A→B→C→D),深层元件的输入信号不能正确更新,导致输出错误。
3.Multiplexer数据输入要求过严:Multiplexer的isReady()要求所有数据输入都准备好,但实际只需要选中的数据输入。在某些电路连接中,未连接的数据输入端阻止了元件的正常计算。
4.无效信号值传播:译码器和数据分配器的无效输出(值为-1)被当作有效信号传播,导致下游元件接收到错误值。
(4)心得:第二次作业让我认识到控制引脚与数据引脚的区别。三态门的控制引脚为低电平时输出高阻态(无效),这种状态管理增加了程序的复杂度。译码器的多输出特性要求我为每个输出引脚生成唯一的信号键。
2.3数字电路模拟程序-3:数据选择器与分配器
(1)作业需求:数字电路是一种处理离散信号的电子电路。与处理连续变化信号(如声音、温度)的模拟电路不同,数字电路只识别和运算两种基本状态:高电平(通常表示为“1”) 和 低电平(通常表示为“0”)。这正好与二进制数制系统相对应,使得数字电路成为所有计算机和数字系统的物理实现基础。
(2)类图:

(3)BUG分析:
1. 连接验证逻辑错误:在 validateConnection 方法中,判断输出/输入的标准有问题,也就是说,在连接信息中,元件的输入引脚(如 A(2)1-1)在"连接系统"中充当的是输出角色。但代码的 isOutputSource 没有正确识别这一点。
2.子电路输出映射不完整:子电路内部可能有多个连接引用同一个输入引脚,但代码只处理了 subParts[0].equals(pinName) 的情况。如果子电路内部输入A连接到多个门,代码需要处理所有情况。
3.getPinValue 递归问题:最后一个递归查找中,如果 conn.outputPin.equals(pinName),又调用 getPinValue(conn.outputPin),这会无限递归。
(4)心得:第三次作业是前两次的整合与提升。信号传播从简单的一轮传播到循环迭代再到事件驱动,经历了多次重构。位运算的理解(MSB vs LSB)是本次作业的关键难点。
三、踩坑心得:
3.1坑点1:题目太过复杂,导致读题都出现错误,理解不当,使得代码逻辑混乱,根本无法有效的修改代码,总结一句话就是题目一开始都没有读懂。
3.2坑点2:输出问题,PTA上给的检查点根本不知道是有关哪方面的,根本无法准确的知道自己到底是哪里出现了错误,然后自己就跟一个无头苍蝇一样找自己代码的错误,但依旧无法成功修改。
3.3坑点3:类图太长了,一般我都记不住一些类中的方法,导致总是需要回头去看我这个类里面的方法到底是怎么使用的,我感觉我设计的类还是太过繁琐了,对实际问题的实现还是缺乏一些效果。
3.4坑点4:对于输出的格式是真的恶心,总是漏一点符号,总结来说就是太复杂了。
四、改进建议:
4.1信号传播算法优化:循环迭代算法时间复杂度O(I×C×N),大型电路效率低。
改进建议: 使用事件驱动仿真算法
4.2增加单元测试覆盖:缺少系统化的测试用例。
改进建议:多加入一些能够让人一眼看得懂的测试点
4.3引入设计模式:Component基类职责过重。
改进建议:使用策略模式分离计算逻辑
4.4数据结构优化: 使用线性查找匹配连接关系。
改进建议:使用HashMap建立信号到连接关系的索引
五、总结
5.1 学到的知识:
(1)面向对象设计: 通过抽象基类Component和多个子类,深刻理解了继承、多态和模板方法模式的应用。抽象方法 compute() 和 isReady() 由子类实现,体现了“定义框架,延迟实现”的设计思想。
(2)算法设计能力: 信号传播从单轮传播→循环迭代→事件驱动,经历了三次重构。理解了算法复杂度分析的重要性,以及不同场景下选择合适的算法策略。
(3)位运算理解: 数据选择器/分配器的控制引脚位序(MSB vs LSB)是本次作业的核心难点。通过反复测试验证,最终正确理解了引脚0是最低位的设计要求。
(4)调试技巧: 学会了通过日志追踪信号传播路径,使用最小化测试用例定位问题,通过对比期望输出和实际输出分析错误根因。
(5)代码组织: 体会了迭代开发的流程,从简单门电路到复杂级联系统逐步完善。工厂方法 getOrCreateComponent 封装了元件创建逻辑,提高了代码可维护性。
5.2 需要进一步学习的内容:
(1)设计模式: 当前代码存在一定的耦合性,学习策略模式、观察者模式可以进一步优化信号传播机制。
(2)数据结构与算法: 事件驱动仿真需要优先队列等数据结构,学习图论可以更好地分析电路拓扑结构。
(3)软件测试: 学习JUnit编写自动化测试用例,建立回归测试体系,避免修复Bug时引入新问题。
(4)并发编程: 大型电路仿真可考虑并行计算提高效率,学习Java多线程编程。
(5)硬件描述语言: 学习Verilog/VHDL的基本语法,理解硬件设计与软件仿真的关系。
5.3 对课程的建议:
(1)样例覆盖增强: 当前样例偏向正常情况,建议增加边界情况样例(如0输入、全1输入、深层级联),帮助学生发现隐藏Bug。
(2)调试工具教学: 建议在实验环节教授SourceMonitor、PowerDesigner等工具的使用,帮助学生分析代码质量。
(3)代码审查环节: 可以组织同学之间互相审查代码,学习不同的设计思路,提升代码质量意识。
(4)阶段性反馈: 建议在每次作业提交后提供典型错误总结,帮助学生在下次作业中避免类似问题。
