数字电路模拟程序三次迭代开发总结
一、前言
作业概况
本次课程的三次作业围绕“数字电路逻辑模拟”这一核心主题,从最基础的门级电路开始,逐步演进到包含三态门、译码器、多路选择器、数据分配器以及可嵌套子电路的完整数字系统仿真平台。每次迭代不仅增加了新元件类型,更在架构设计、错误处理和可扩展性上实现了质的飞跃。
第一次作业:实现基本门电路(与、或、非、异或、同或)的建模与信号传播,支持多输入与门/或门,输入信号赋值,输出按指定顺序打印。
第二次作业:引入三态门(Tri‑State)、译码器(Decoder)、多路选择器(Mux)、数据分配器(Demux),拓展了元件库,采用面向对象的继承体系统一管理不同组件,并实现了通用的信号传播机制。
第三次作业:增加子电路(SubCircuit)定义与实例化,支持模块化设计;同时实现了全面的输入合法性校验,包括连接语句中的多输入源、无输入/输出、顺序错误以及输入信号冲突等异常检测,极大地提升了程序的鲁棒性。
知识点覆盖
层次 第一次 第二次 第三次
语言基础 类与对象、HashMap、正则表达式 抽象类、继承、多态 静态方法、工具类设计、异常处理
数据结构 ArrayList、HashMap Map、List、数组 复杂嵌套Map、队列传播
算法 简单信号传播(固定迭代) 基于变化循环的传播 拓扑排序思想、多级传播
设计模式 简单工厂(switch创建门) 模板方法(evaluate/propagate) 策略模式(验证器)、模块化
领域知识 门电路逻辑 组合逻辑元件(选择器、译码器等) 子电路嵌套、层次化设计
题量与难度
作业 题量 难度 核心挑战
第一次 小(约200行) 低 正确解析连接格式,实现多输入门计算
第二次 中(约400行) 中 统一管理多种组件,正确传播三态门的高阻态语义
第三次 大(约800行) 高 子电路内部传播、复杂错误检测、迭代收敛控制
二、设计与分析
第一次作业
代码结构
第一次作业采用最直接的过程式思路:Gate 类封装门类型、输入数量、输入映射,Main 类负责解析输入、构建连接、循环传播和输出。
类图

圈复杂度分析

main() 方法负责了输入读取、门实例创建、信号传播、排序输出等全部流程,圈复杂度较高,但整体代码量小,尚可接受。
设计亮点与不足
亮点:使用 HashMap 存储输入引脚映射,支持任意顺序的输入连接;利用正则表达式灵活解析门名称。
不足:传播算法采用简单循环,未考虑依赖顺序,可能导致多次冗余迭代;所有逻辑堆砌在 Main 中,可维护性差。
第二次作业
代码结构
第二次作业对架构进行了彻底重构,引入抽象类 Component,并派生出 Gate(覆盖五种门)、TriState、Decoder、Mux、Demux 五个具体子类。每个子类都实现 evaluate() 和 propagateOutputs() 方法,形成统一的传播模板。组件由 Map<String, Component> 统一管理,传播过程按类型顺序执行。
类图

Main 类职责大幅减轻,仅负责输入解析、构建组件、调用传播,并最终输出。
圈复杂度分析

setInput() 方法的圈复杂度较高,因为它需要处理五种不同类型组件的引脚赋值,使用了多个 if-else 判断。这提示我们可以采用访问者模式或策略模式进行优化。
设计亮点与不足
亮点:抽象类统一了传播接口,新增元件只需继承 Component 并实现 evaluate/propagate,符合开闭原则;传播循环基于 changed 标志,避免了无谓迭代。
不足:setInput 中的类型判断过于集中,破坏了封装性;Decoder 的输出引脚较多,传播时逐个设置,不够优雅;排序输出时仍依赖硬编码顺序,扩展性不佳。
第三次作业
代码结构
第三次作业在第二次基础上增加了 子电路(SubCircuit) 支持,允许将一组元件封装为可复用的模块,并可在顶层或更高层次引用。子电路有自己的输入/输出端口、内部门电路和连接关系。同时,本次还加入了完整的输入校验模块,包括连接语句的合法性、输入信号冲突检测等。
类图


子电路内部拥有独立的 Gate 实例和信号映射,顶层传播时会递归调用子电路的 propagate()。错误检测被提取为静态方法(如 validateConnection、validateSubCircuitConnection),独立于主流程。
圈复杂度分析

propagateSignals() 方法复杂度最高,因为它需要协调顶层和子电路的信号交换,同时处理信号冲突检测。但整体功能划分仍较为清晰。
设计亮点与不足
亮点:
子电路设计实现了层次化建模,支持模块复用。
输入验证全面覆盖了常见的格式错误,提高了程序健壮性。
错误检测与传播逻辑分离,便于测试和维护。
不足:
传播算法仍采用“固定最大迭代次数”的试探性循环,未基于拓扑排序保证单次传播完成,存在效率隐患。
子电路与顶层的信号映射依赖字符串拼接(如 "C" + name + "-" + out),易出错且不直观。
验证函数中存在大量重复代码(如对子电路和普通连接的校验逻辑几乎相同),未进行抽象复用。
三、采坑心得
- 输入解析的“幽灵换行符”问题
现象:在第二次作业中,我试图使用 scanner.nextInt() 读取数字后再用 nextLine() 读取后续行,结果发现 nextLine() 直接返回空串。
原因:nextInt() 不消耗末尾换行符,导致后续 nextLine() 读到空行。
解决:从第一次作业起,我就坚持全程使用 scanner.nextLine() 读取整行后再手动解析(如 split、正则匹配),彻底规避了该问题。这一习惯在后续复杂输入(包含子电路定义)中依然有效,是保证稳健输入的基础。
- 信号传播的无限循环与收敛判断
现象:第二次作业中,若电路中存在反馈(如将输出接回输入),传播循环可能陷入无限更新。
解决:引入了 changed 标志和最大迭代次数(如100次),并在每次迭代后检测是否还有变化。同时,对于三态门,当控制端为0时,输出端被置为高阻态(valid=false),此时不再传播,避免了循环依赖。第三次作业中,子电路嵌套加深,但通过逐层传播和外部信号同步,依然保持了稳定性。
- 子电路内部与外部信号命名冲突
现象:第三次作业中,子电路内部信号名可能与顶层信号重名,导致误赋值。
解决:所有子电路的输出在顶层均以 "C" + name + "-" + internalPin 格式命名,输入也通过该前缀映射。内部传播时使用独立 internalSignals 映射,与顶层隔离。这一设计虽然增加了字符串处理,但成功实现了层次化隔离。
- 错误检测的优先级顺序
现象:题目要求对连接语句进行多种错误检测,且需按特定优先级报告(如“输入信号冲突”优先级最高,“输入输出顺序错误”最低)。
解决:在 validateConnection 方法中,我严格按照题目给定的优先级顺序依次检查:先统计输入源数量,若>1则报冲突;若=0则报无输入;若无输出则报无输出;最后检查顺序。通过多次测试用例验证,确保了错误信息与预期一致。
- 字符输入错误导致大量调试时间
现象:在第三次作业中,因将 SubCircuit 拼写为 Subcircuit,导致编译错误,花费大量时间排查。
教训:养成使用 IDE 自动补全和代码模板的习惯,减少手动输入错误。同时,在编写长代码时,定期进行编译检查,及早发现语法问题。
四、改进建议
-
传播算法优化:基于拓扑排序的单次遍历
当前传播采用迭代循环,最坏情况下可能执行多达100轮。对于无反馈的组合电路,可先构建元件的依赖图,按拓扑顺序一次传播即可完成。这将大幅提升大规模电路的仿真效率。 -
统一错误处理机制
目前错误检测直接返回字符串,并在 main 中打印后 return。更好的做法是定义自定义异常类(如 CircuitException),在检测到错误时抛出,由统一异常处理器捕获并输出。这样可以将校验逻辑与主流程解耦,也便于单元测试。 -
消除 instanceof 分支,采用访问者模式
setInput 方法中根据组件类型执行不同赋值逻辑,这种集中式判断违反了开闭原则。可让每个组件实现 accept(Visitor) 方法,由访问者负责引脚赋值,新增组件时只需添加新的访问者实现,无需修改已有代码。 -
子电路接口封装优化
目前子电路的输入/输出端口使用 List存储名称,与内部信号的映射依赖字符串前缀。可以定义 Port 类,包含名称、方向、关联信号等属性,使接口更清晰,并便于静态检查。 -
增加单元测试与日志
为提高可靠性,可为每个组件编写独立的单元测试(使用 JUnit),验证其逻辑正确性。同时引入日志框架(如 java.util.logging),在传播过程中记录关键状态变化,便于调试复杂电路的异常行为。
五、总结
关键学习收获
面向对象思维
通过三次迭代,我深刻体会到抽象类和继承的强大威力——第二次引入 Component 后,新增元件只需添加子类,核心传播代码几乎无需改动。第三次的子电路设计更是将“组合优于继承”的理念付诸实践。
复杂问题拆解能力
面对多类型元件、层次化结构和严格错误检测,我学会了将大问题分解为独立模块:解析、元件工厂、传播引擎、校验器、输出格式化,每个模块可单独开发和测试。
代码质量意识
从第一次的“能跑就行”到第三次的“可读、可维护”,我逐渐养成了编写注释、抽取常量、避免冗长方法的习惯。虽然 propagateSignals 仍较复杂,但相比第一次的 main 方法已有了质的进步。
调试与测试能力
每次作业都离不开大量手工构造测试用例,尤其是边界情况(如输入信号冲突、多输入无输出等)。这让我学会了如何系统性地设计测试输入,并快速定位逻辑缺陷。
通过这三次作业,我不仅掌握了数字电路仿真的基本技术,更重要的是学会了如何通过持续重构和设计演进,构建一个可扩展、健壮的系统。这些经验将伴随我在未来的编程实践中不断成长。
浙公网安备 33010602011771号