第4~6次迭代作业总结
一、前言
本次作业集涵盖了数字电路模拟程序的第1次、第2次和第4次迭代,整体围绕组合逻辑电路的建模、仿真与异常处理展开。从知识点来看,三次迭代逐步引入了面向对象设计、抽象类与继承体系、集合框架、字符串解析、递归/迭代模拟以及异常检测机制,是对Java面向对象编程能力的一次系统性训练。
从题量来看,虽然只有三次迭代,但每次迭代的代码量和逻辑复杂度呈指数级增长。第1次迭代仅需实现基础逻辑门(与、或、非、异或、同或)的连接与运算;第2次迭代增加了三态门、译码器、数据选择器和数据分配器等复杂元件,并引入了控制引脚的概念;第4次迭代则进一步引入了子电路定义(C:前缀)、输入输出合法性校验、信号冲突检测等高级特性,对程序的健壮性和可扩展性提出了极高要求。
从难度来看,核心难点不在于单个逻辑门的实现,而在于:(1)如何设计一个灵活的类层次结构来统一管理不同类型的元件;(2)如何实现信号在电路中的传播与稳定判定;(3)如何在解析输入时高效地处理各种异常场景。整体而言,这三次迭代是一次从"能跑"到"能维护"再到"能容错"的完整工程实践。
二、设计与分析
2.1 类图设计与面向对象架构
在本次数字电路模拟程序的设计中,我采用了PowerDesigner进行类图建模。整体架构围绕"端口-元件-电路"三层抽象展开。



从类图中可以清晰看到,PUT类作为端口/引脚的抽象,包含名称、状态和连接状态三个核心属性,是整个电路拓扑的基础单元。Door作为抽象基类,定义了translate()(逻辑运算)、output()(输出结果)、setInputPUT()(设置输入引脚)等通用接口,所有具体门电路类(AndDoor、OrDoor、NotDoor、XDoor、YDoor)以及第2次迭代新增的TriStateDoor、Decoder、Mux、Demux均继承自该类,并重写translate()方法以实现各自的逻辑语义。
这种设计的优势在于:当需要新增一种逻辑门时,只需新建一个子类并重写translate()方法,无需修改任何已有代码,完全符合开闭原则(OCP)。同时,CircuitCreator类作为电路的构建与模拟引擎,通过多态方式统一调用所有Door子类的translate()方法,实现了逻辑运算与电路拓扑的解耦。
在第4次迭代中,子电路的引入对类图提出了新的挑战。我在CircuitCreator中增加了子电路定义解析模块,将子电路的内部连接关系以Map结构缓存,在实例化时动态映射输入输出端口。这一设计避免了为每个子电路创建独立类的冗余,体现了"组合优于继承"的设计思想。
2.2 SourceMonitor复杂度分析



从SourceMonitor生成的报表来看,CircuitCreator类的圈复杂度(Cyclomatic Complexity)显著高于其他类,主要集中在parseLine()、checkErrors()和simulateCircuit()三个方法中。这是因为这三个方法需要处理大量的条件分支:输入格式的多种变体、异常类型的多重判断、以及模拟循环中的收敛条件判定。
具体而言,checkErrors()方法的复杂度最高,因为它需要逐一检测"多输入源"、"无输入"、"无输出"、"信号冲突"等多种异常场景,每种场景都需要遍历连接列表并进行交叉验证。从数据上看,该方法的分支数达到了15个以上,远超建议的10个上限。
simulateCircuit()方法的复杂度则来源于模拟循环的双重终止条件:状态稳定判定和最大迭代次数限制。这种设计虽然保证了程序的终止性,但也增加了逻辑分支的数量。
相比之下,各个Door子类的复杂度均控制在较低水平(通常为2-3),这得益于抽象基类对通用逻辑的封装,以及每个子类仅关注自身逻辑运算的单一职责设计。
2.3 设计心得
通过本次设计,我深刻体会到:在复杂系统中,类图的提前规划比代码编写更为重要。第1次迭代时我急于编码,导致第2次迭代新增元件时不得不大幅重构CircuitCreator中的条件判断逻辑。而在第4次迭代前,我先用PowerDesigner完成了类图建模,明确了子电路的映射机制,使得编码过程顺畅了许多。
此外,SourceMonitor的量化反馈让我意识到:高复杂度并不等于"功能强大",它往往意味着职责过重。CircuitCreator承担了太多本应分离的职责(解析、校验、构建、模拟),在后续改进中应考虑将其拆分为Parser、Validator、Builder、Simulator四个独立类。
三、采坑心得
3.1 信号传播的死循环问题
在第1次迭代测试阶段,我遇到了一个严重的Bug:当电路中存在反馈回路(如将非门的输出连回自身的输入)时,simulateCircuit()陷入无限循环。
问题根因:初始版本的模拟循环仅依赖"状态是否变化"作为终止条件,而振荡电路的状态永远在变化。
解决方案:引入最大迭代次数限制(设为100次),超过该次数后强制终止并标记为"未收敛"。同时,在每次迭代中记录每个门的输出快照,只有当快照与上一轮完全一致时才判定为稳定。
数据佐证:修复前,含反馈回路的测试用例运行时间超过30秒仍未返回;修复后,所有测试用例均在0.1秒内完成,且振荡电路被正确标记为未收敛状态。
3.2 子电路端口映射的顺序依赖Bug
在第4次迭代的子电路实现中,我曾遇到一个隐蔽的Bug:子电路的输入输出端口映射依赖于解析顺序,当输入定义的顺序与子电路定义中的端口声明顺序不一致时,信号被错误地连接到错误的端口。
问题根因:最初使用List存储子电路端口,按解析顺序索引映射。但输入定义的顺序并不保证与声明顺序一致。
解决方案:将端口存储改为Map<String, PUT>,以端口名称为键进行精确映射,彻底消除顺序依赖。
测试结果:修复前,10组子电路测试用例中有4组输出错误;修复后,全部通过。
3.3 异常检测的遗漏与误报
checkErrors()方法在初期开发时经历了多轮调试。最初仅检测了"多输入源"和"无输出"两种异常,导致"信号冲突"(同一引脚被两个不同值的源驱动)未被捕获,在OJ上大量WA。后来增加了冲突检测,却又因为检测逻辑过于严格,将合法的三态门总线连接误判为冲突。
解决过程:通过引入三态门的"高阻态"概念,在冲突检测中排除高阻态引脚,最终实现了准确的异常判定。这一过程让我认识到:异常处理不能简单地"一刀切",必须结合具体元件的语义进行精细化设计。
四、改进建议
4.1 职责分离与单一职责原则
当前CircuitCreator类承担了过多职责,建议拆分为:
CircuitParser:负责输入解析与子电路定义缓存
CircuitValidator:负责所有异常检测逻辑
CircuitBuilder:负责元件实例化与连接建立
CircuitSimulator:负责信号传播与收敛判定
这种拆分不仅能降低单个类的复杂度,还能使每个组件可独立测试。
4.2 引入观察者模式优化信号传播
当前的模拟采用"全量遍历"方式,每轮迭代都检查所有门的状态变化。对于大规模电路,这带来了不必要的计算开销。建议引入观察者模式:每个PUT维护一个下游监听器列表,当状态变化时仅通知直接下游的门进行重新计算,实现增量传播。
4.3 增强输入解析的容错性
当前的parseLine()方法对输入格式要求极为严格,任何格式偏差都会导致解析失败。建议引入正则表达式进行格式预校验,并在解析失败时提供具体的错误位置和提示信息,提升用户体验。
4.4 增加单元测试覆盖
本次开发过程中,大量Bug依赖OJ提交后才发现,调试效率低下。建议为每个Door子类和CircuitValidator编写JUnit单元测试,在本地即可完成核心逻辑的验证。
五、总结
通过作业集4~6的三次迭代,我在以下方面获得了实质性提升:
第一,面向对象设计能力。从最初将所有逻辑塞进一个类,到后来能够合理运用抽象类、继承、多态和组合关系构建可扩展的类层次结构,这是从"写代码"到"设计软件"的关键跨越。
第二,复杂系统的调试能力。信号传播的死循环、端口映射的顺序依赖、异常检测的误报等问题,让我学会了如何通过数据快照、日志追踪和边界条件分析来定位隐蔽Bug,而非盲目修改代码。
第三,工程化思维。SourceMonitor的量化反馈、PowerDesigner的类图建模、OJ的自动化测试,让我认识到软件工程不仅是编码,更是设计、度量、测试和持续改进的闭环。
仍需进一步学习的方面包括:设计模式的深入应用(如策略模式替代大量if-else、工厂模式简化元件创建)、性能优化技术(如增量模拟、拓扑排序避免全量遍历)、以及更完善的测试体系搭建。这些将是我下一阶段的重点研究方向。

浙公网安备 33010602011771号