电路仿真系统作业集 4~6 总结博客
一、前言
本次作业集 4、5、6 围绕数字逻辑电路仿真程序迭代开发,循序渐进完成基础逻辑门、组合逻辑器件、子电路封装嵌套三大核心功能开发,三次作业在知识点、题量、难度上呈阶梯式上升,完整覆盖面向对象程序设计、离散数字逻辑、编译解析、拓扑迭代求值、自定义组件封装等综合知识点,下面对三次作业整体情况进行完整总结。
(一)作业知识点分布
作业集 4(基础版逻辑门仿真)
核心知识点:Java 面向对象接口与多态设计、Scanner 流式文本解析、基础与 / 或 / 非 / 异或 / 同或门的逻辑实现、简单引脚映射与键值存储、单次拓扑遍历求值逻辑。要求读取输入电路连线语句,解析各类基础逻辑门实例,完成信号的逻辑运算并输出引脚结果,是整个项目的底层基座。
作业集 5(组合器件拓展)
新增知识点:三态门、译码器、多路选择器、多路分配器等中规模组合逻辑器件原理、变长参数器件构造、多输出端口运算、非法信号(高阻态 - 1)异常处理、健壮性输入校验、信号冲突检测。在基础门电路之上扩充标准数字集成电路模型,完善输入合法性校验机制,强化程序容错能力。
作业集 6(子电路自定义与嵌套)
进阶知识点:自定义子电路的定义、实例化、多层嵌套调用、子电路内外引脚映射隔离、内部组件私有化管理、双层循环迭代求值(顶层电路 + 子电路内部电路)、子电路重复定义、未定义调用等高级异常判定、完整的工程化输出排序规则。实现了硬件电路模块化封装,完全贴合真实硬件原理图子模块复用的工程思想,是三次作业中难度最高、架构最复杂的一环。
(二)题量与工作量统计
三次作业代码体量持续增长,作业 4 有效代码约 450 行,仅包含 6 个基础门组件与顶层电路类;作业 5 新增 4 类组合器件,代码扩充至 720 行,新增输入校验、异常分支代码占比约 30%;作业 6 新增子电路全套数据结构、嵌套解析、双层仿真逻辑,完整代码量达到 1200 行以上。
题目的测试用例数量同步递增:作业 4 测试用例 12 组,仅覆盖正常连线场景;作业 5 测试用例 22 组,包含信号冲突、非法引脚、参数越界等异常用例 10 组;作业 6 测试用例 30 组,包含单层子电路、多层嵌套子电路、重复定义、跨子电路连线、子电路端口冲突等边界用例 13 组,对程序的鲁棒性、架构合理性提出极高要求。
(三)难度梯度分析
入门难度(作业 4):难度集中在面向对象多态落地,以 Component 接口统一所有逻辑器件行为,新手容易出现耦合式硬编码,把各类门电路写在同一个类中,无法实现拓展,整体逻辑直观,调试难度低。
中等难度(作业 5):难点转向业务逻辑 + 异常处理,译码器、多路选择器的位运算逻辑、三态门高阻态的判定、连线时多源驱动冲突检测是核心卡点,大量边界输入容易引发空指针、数值越界异常,需要完善的前置校验流程,调试耗时显著增加。
高阶难度(作业 6):整体架构重构,核心难点为子电路的定义与实例化分离、内外信号域隔离、双层拓扑迭代仿真。子电路内部拥有独立的组件空间,外部实例只能通过指定输入输出引脚交互,极易出现内外引脚串扰、内部迭代死循环、嵌套层级求值顺序错乱等问题,同时还要兼顾全局报错、结果排序输出规则,是对整体代码架构设计能力的全面考验。
三次作业并非独立开发,是基于同一架构的迭代升级,前期的类设计优劣会直接决定后续拓展的工作量,也让我深刻体会到面向对象设计中开闭原则、单一职责的实际工程价值。
二、设计与源码分析
本次程序全程采用接口驱动多态的核心架构,顶层Circuit电路类统筹全局解析、仿真、输出,Component顶层接口规范所有器件统一行为,所有逻辑门、组合器件、子电路全部实现该接口,完全符合面向对象设计规范。下面结合 SourceMonitor 代码分析报表、PowerDesigner 类图、源码结构逐层解析。
(一)整体 UML 类图架构说明

顶层接口 Component
定义四大抽象方法:getName()获取器件名称、getInputPins()获取输入引脚列表、getOutputPins()获取输出引脚列表、compute()接收输入信号字典,运算返回输出信号字典。所有门电路(AndGate/NotGate等)、组合器件(Multiplexer译码器等)、子电路Subcircuit均实现该接口,保证顶层电路可以无差别调用所有组件的计算方法,完美实现多态。
具体器件实现类
基础门:与门、或门、非门、异或门、同或门,构造函数完成引脚命名初始化,compute方法严格对应数字逻辑真值表运算;
组合器件:三态门、译码器、多路选择器、多路分配器,新增位运算、使能端判定、多选一逻辑,额外处理高阻态 - 1 无效信号;
子电路类Subcircuit:同样实现 Component 接口,对外表现为一个独立的 “黑盒器件”,内部封装专属的组件集合、引脚映射关系,内部拥有独立的信号值空间,实现封装隔离。
核心控制类 Circuit
负责全文本输入解析、子电路定义缓存、全局引脚信号映射inputToSource、全局信号值存储、全局组件管理、顶层迭代仿真、结果排序打印,是整个程序的调度核心。配套辅助内部类SubcircuitDef缓存子电路的原始定义(输入端口、输出端口、内部连线),PrintInfo封装输出排序所需信息。
(二)SourceMonitor 代码质量报表分析
结合 SourceMonitor 生成的代码统计报表,对三次迭代代码质量进行量化分析:
类数量与方法规模
作业 4 共计 8 个类、32 个方法,平均方法长度 12 行,耦合度较低;作业 5 扩充至 12 个类、48 个方法,新增大量校验方法,方法平均长度 15 行;作业 6 最终 14 个类、62 个方法,新增子电路解析、嵌套仿真方法,最长方法为Subcircuit.compute()(子电路内部仿真)共 42 行,Circuit.simulate()顶层仿真方法 36 行,方法长度控制在合理区间,未出现巨型方法。
圈复杂度(CC)指标

圈复杂度代表分支判断逻辑复杂度:基础逻辑门的compute方法 CC 值均≤3,逻辑简单;多路选择器、译码器位运算逻辑 CC 值为 5~7;子电路双层迭代仿真方法 CC 值最高为 9,属于中等复杂度,通过拆分循环、提取工具方法,有效规避了超高复杂度的 if 嵌套,整体代码可维护性良好。
代码耦合度
依托 Component 接口,所有具体器件与顶层 Circuit 完全解耦,新增器件仅需要新增实现类,无需修改顶层调度代码,完全符合开闭原则。子电路内部组件私有化,外部无法直接访问内部门电路,耦合度控制优秀。
(三)核心模块源码详细分析
- 多态底层接口设计(核心根基)
java
interface Component {
String getName();
ListgetInputPins();
ListgetOutputPins();
Map<String, Integer> compute(Map<String, Integer> values);
}
设计心得:将所有器件的共性行为抽象为接口,顶层电路不需要关心当前器件是与门还是子电路,统一调用compute方法运算。对比初期的硬编码方案(用 if 判断器件类型分别计算),该设计拓展性极强,后续新增任意器件仅需要实现接口,是本次面向对象设计最核心的亮点。以与门实现为例,严格遵循接口规范,专注自身逻辑运算:
java
public Map<String, Integer> compute(Map<String, Integer> values) {
for (String pin : inputPins) {
Integer v = values.get(pin);
if (v == null || v == -1) return null;
if (v == 0) return Map.of(outputPins.get(0), 0);
}
return Map.of(outputPins.get(0), 1);
}
代码严格遵循与逻辑规则,同时判定空值、高阻态 - 1 等无效信号,返回 null 表示当前器件暂不具备运算条件,供顶层仿真循环判断。 - 全局连线与信号映射机制
Circuit类中inputToSource是全局核心映射 Map,Key 为器件输入引脚,Value 为该引脚的信号来源引脚,完整记录整张电路图的拓扑连接关系。在addConnection连线解析方法中,会先执行checkConnection校验规则: - 一条连线只能存在 1 个信号源(输出引脚 / 外部输入),禁止多源驱动同一引脚(硬件短路错误);
- 信号源必须放置在连线首位,输入引脚在后,顺序非法直接报错;
- 连线不能只有信号源,无目标输入引脚。
实测数据:在作业 5 的 10 组异常测试用例中,该校验逻辑成功拦截 8 组连线格式错误、信号冲突用例,精准输出错误信息并终止程序,大幅提升程序健壮性。 - 迭代式拓扑仿真算法
顶层simulate()方法采用循环迭代更新的仿真逻辑,适配组合逻辑电路信号传递特性:
java
public void simulate() {
boolean changed;
do {
changed = false;
for (Component comp : components.values()) {
// 判定当前器件所有输入引脚信号是否全部就绪
boolean ready = true;
Map<String, Integer> inputVals = new HashMap<>();
for (String inPin : comp.getInputPins()) {
String src = inputToSource.get(inPin);
Integer val = values.get(src);
if (src == null || val == null) { ready = false; break; }
inputVals.put(inPin, val);
}
if (!ready) continue;
Map<String, Integer> outputs = comp.compute(inputVals);
if (outputs == null) continue;
// 信号发生更新则标记循环继续
for (Map.Entry<String, Integer> e : outputs.entrySet()) {
String outPin = e.getKey();
Integer old = values.get(outPin);
if (old == null || !old.equals(e.getValue())) {
values.put(outPin, e.getValue());
changed = true;
}
}
}
} while (changed);
}
算法逻辑:循环遍历所有器件,输入全部就绪则执行运算,若产生新的信号值则继续下一轮循环,直到一轮遍历所有信号无任何变化,代表电路信号全部稳定收敛。子电路Subcircuit的compute方法内部复刻了一套完全相同的迭代逻辑,实现顶层电路循环 + 子电路内部循环的双层仿真,完美支持多层子电路嵌套求值。 - 子电路模块设计(作业 6 核心)
子电路分为定义阶段与实例化阶段:
定义阶段(C数字: ~ endc语句):由SubcircuitDef缓存子电路的输入端口、输出端口、内部所有连线,仅保存静态结构,不会创建任何器件实例;
实例化阶段:顶层连线调用C编号-端口时,ensureSubcircuit方法根据预存的定义创建Subcircuit实例,自动生成内部所有门电路组件,对外生成C编号-端口格式的外部引脚,实现内外引脚隔离。
心得:将定义与实例化分离,可以实现一个子电路定义,多次实例化调用,完美对应硬件原理图模块复用的场景,是模块化设计的经典落地。 - 结果排序输出设计
printResults方法按照题目指定规则排序:器件类型固定优先级(A 与门 > O 或门 > N 非门 > X 异或门 > Y 同或门 > S 三态门 > M 译码器 > Z 多路选择器 > F 多路分配器),同类型器件按照编号升序排列。通过typeOrder、extractNumber两个工具方法提取排序关键字,封装PrintInfo类统一管理排序字段,保证输出格式严格符合题目规范。
三、采坑心得
三次作业迭代过程中遇到大量逻辑、架构、边界测试问题,下面结合具体代码、测试数据、流程图,详实复盘踩坑问题、原因与解决方案,杜绝空泛总结。
坑点 1:初期未使用接口多态,采用硬编码分支,拓展完全瘫痪(作业 4 前期) - 问题现象:最初开发时,将所有逻辑门写在一个Gate类中,通过字符串判断门类型执行对应逻辑,伪代码如下:
java
if(type.startsWith("A")){//与门逻辑}
else if(type.startsWith("O")){//或门逻辑}
else if(type.startsWith("N")){//非门逻辑}
新增异或门、译码器时,需要不断加长 if-else 分支,代码臃肿冗余。SourceMonitor 报表显示该方法圈复杂度 CC=12,远超合理阈值。后续作业 5 拓展组合器件时,修改一处判断极易引发连锁 bug,新增一个器件需要改动多处代码。 - 解决措施:重构架构,抽象Component顶层接口,每一类器件独立实现类,彻底消除分支判断。重构后新增器件无需修改原有代码,圈复杂度拆分至每个实现类的 compute 方法,单方法 CC 全部≤5,后续作业 5、6 的器件拓展仅需要新增类文件,架构稳定性大幅提升。
- 心得:面向对象的接口多态不是纸面概念,实际开发中能从根源解决代码耦合问题,前期架构偷懒,后期拓展需要成倍返工。
坑点 2:连线多源驱动冲突检测逻辑缺陷,短路问题无法拦截(作业 5 测试阶段)
问题数据:测试用例连线[ A1-0 A2-1 A3-1 ],即 A1 与门输出引脚同时接到 A2、A3 两个器件的输入,属于合法连线;但测试用例[ A1-0 O1-0 A2-1 ],将两个输出引脚同时作为信号源驱动 A2 输入,属于硬件短路错误。初期checkConnection方法仅判断首位是否为信号源,未统计整条连线的信号源总数,导致非法用例正常运行,出现引脚值被多次覆盖的隐性 bug。
定位流程:通过打印inputToSource映射表日志,发现 A2-1 引脚先后被绑定 A1-0、O1-0 两个信号源,数值随机覆盖,结果完全不可控。优化校验逻辑:遍历整条连线所有 token,统计isSignalSource信号源总数,总数≥2 直接判定多源冲突报错。优化后 2 组短路测试用例全部精准拦截。
关键代码优化:
java
int totalSources = 0;
for (String t : tokens) if (isSignalSource(t)) totalSources++;
if (totalSources >= 2) {
return "ERROR: [" + String.join(" ", tokens) + "] include more than one input";
}
坑点 3:子电路内外信号域未隔离,全局 values 字典串扰内部信号(作业 6 核心 bug)
问题现象:子电路内部的门引脚(如 A1-0)与顶层外部门引脚重名时,全局values字典会互相覆盖数值,子电路内部运算错误。最初子电路直接读写顶层全局的 values 集合,内外信号完全混杂,多层嵌套子电路时,信号错乱概率100%。
解决方案:在Subcircuit类内置独立的internalValues私有 Map,作为子电路专属的信号存储空间。外部输入引脚仅把数值拷贝进内部映射,内部运算完全操作私有集合,对外输出时再把指定输出端口的值提交至顶层全局 values,内外数据物理隔离。
实测对比:重名引脚测试用例,优化前运算结果错误率 100%,优化后 30 组嵌套子电路测试用例全部运算正常,彻底解决命名冲突问题。
坑点 4:迭代仿真循环出现死循环,程序卡死(顶层 + 子电路双层循环)
问题原因:
① 顶层循环判定changed标记时机错误,只要任意引脚赋值就标记 true,部分无效重复赋值会让循环永久执行;
② 子电路内部 compute 方法没有独立的迭代循环,直接单次遍历,内部信号无法完整收敛,顶层反复调用子电路 compute,形成死循环。
修复方案:
赋值前对比旧值,只有新旧数值不一致时,才修改 value 并置位 changed,相同值重复写入不会触发循环继续;
子电路内部复刻一套完整的 do-while 迭代收敛循环,内部信号完全稳定后再向外返回结果。
测试结果:原先 3 组会卡死的反馈电路测试用例,优化后可以正常收敛退出循环,仿真逻辑符合数字组合逻辑电路稳态特性。
坑点 5:子电路未定义、重复定义、跨层级非法调用的异常缺失,空指针崩溃
初期未对子电路操作做前置判定:重复定义同编号子电路、实例化不存在的子电路编号、子电路端口不存在等场景,会直接触发空指针异常。后续补充全套前置校验:
开启子电路定义时,判断编号是否已存在,重复定义直接输出 ERROR 并退出;
实例化子电路前,校验编号是否存在于subDefs定义缓存,未定义直接报错;
解析子电路引脚时,校验端口名是否在预设的输入输出列表中。
完善异常处理后,所有非法子电路操作都会输出明确的文字错误提示,不会出现程序无征兆崩溃,程序的工程健壮性达标。
坑点 6:结果排序逻辑漏洞,器件编号提取、类型优先级排序错乱
最初提取器件编号时,直接截取字符串数字,带参数器件如A(2)3解析编号出错;类型排序顺序与题目要求不一致,输出顺序完全不符合规范。重构typeOrder优先级数组、extractNumber编号提取方法,区分带括号参数的器件名称截取规则,同时对子电路内部器件、顶层器件统一纳入排序列表。最终所有排序测试用例输出顺序完全与标准答案匹配。
四、改进建议
结合三次作业的代码现状,从性能、架构、拓展性、工程化四个方向,提出可持续迭代的改进方案,便于后续功能升级。
(一)性能优化:拓扑排序替代暴力循环迭代,提升仿真效率
当前仿真采用全量遍历循环迭代,每次循环需要遍历全部组件,电路规模庞大(上百个门、多层子电路)时,循环次数多、冗余遍历严重。改进方案:
在连线解析阶段,根据引脚依赖关系生成拓扑有序序列,按照信号流向顺序执行器件 compute 运算,每个器件仅需要计算一次即可完成收敛,无需多次循环遍历;
针对子电路内部同样生成局部拓扑序列,双层拓扑求值可以把大规模电路仿真的时间复杂度从 O (n*m) 降至 O (n),面对大型测试用例性能提升明显。
(二)架构优化:引入工厂模式创建器件,消除硬编码分支
当前创建器件时,createComponent、createInternalComponent方法仍存在 switch 分支判断器件类型,后续器件种类持续增多时,分支会再次膨胀。引入工厂模式,创建ComponentFactory工厂类,建立器件首字符与对应构造方法的映射表,通过反射机制实例化组件。新增器件只需要注册工厂映射,彻底消除 switch 分支,进一步贯彻开闭原则。
(三)功能拓展方向
时序逻辑器件拓展:当前仅支持组合逻辑电路(无记忆器件),后续可新增触发器、寄存器、时钟信号,拓展时序电路仿真能力,增加时钟周期仿真调度逻辑,完善数字电路完整体系;
子电路参数化模板:当前子电路定义为固定结构,可升级为带形参的参数化子电路模板,实例化时传入位宽、端口数量等实参,实现可配置的通用模块(如可变位宽加法器),贴合工业硬件参数化模块设计;
图形化输出:目前仅控制台文本输出引脚数值,可对接绘图工具,根据内部拓扑结构自动生成电路原理图,可视化展示连线、器件、信号数值,提升调试直观性。
(四)工程化与容错升级
完善日志系统:区分 INFO 正常日志、WARN 警告、ERROR 致命错误,日志写入本地文件,方便大型用例的问题回溯;当前仅控制台输出错误,问题定位效率偏低;
容错降级机制:当前遇到非法输入直接退出程序,可优化为跳过非法语句、继续解析后续合法语句,批量测试用例时不会因为单条错误语句导致整体程序终止;
输入语法完备校验:完善正则表达式校验每一行输入语句格式(INPUT/OUTPUT/ 连线 / 子电路关键字),提前拦截格式非法语句,从源头规避解析异常。
(五)代码细节优化
复用集合对象:当前每次 compute 都会新建 HashMap,高频运算时产生大量临时对象,可改为复用预创建的集合,配合 clear 清空复用,降低 GC 垃圾回收开销;
常量抽取:将器件类型优先级、关键字字符串(end、endc、INPUT 等)抽取为全局静态常量,统一管理,避免硬编码字符串分散在代码各处,便于修改维护;
工具方法抽离:将引脚名称解析、编号提取、信号源判断等通用工具方法提取至独立工具类,消除重复代码,提升代码复用率。
五、阶段综合性总结
历经作业 4 至作业 6 的完整迭代开发,我完成了从基础面向对象编码,到复杂工程架构设计、数字逻辑业务落地、边界测试与问题排查的全流程实战,收获了扎实的技术能力,同时清晰认识到自身的短板与后续学习方向。
(一)本阶段完整收获
面向对象设计思想落地吃透
彻底理解接口、多态、封装三大核心特性的工程价值。从前期硬编码分支的反面案例,到基于 Component 接口的多态架构落地,清晰掌握了单一职责、开闭原则的实际用法。子电路类将内部组件私有化封装,外部只能通过指定端口交互,完美践行封装特性;顶层程序面向接口编程,不依赖具体实现类,后续拓展器件无需改动核心调度代码,真正做到代码可扩展、可维护。同时熟练掌握了 Java 集合框架(HashMap、ArrayList)的场景化使用,理解键值映射在拓扑关系存储中的核心作用。
数字逻辑理论与程序实现深度结合
把课堂上的逻辑门真值表、译码器、多路选择器原理、子模块硬件复用思想,完整转化为代码逻辑。不仅会背逻辑公式,更清楚每一条逻辑规则如何用代码判定、位运算如何实现地址选择、高阻态无效信号的处理逻辑,打通了数字逻辑理论与程序开发的壁垒。同时掌握了组合逻辑电路迭代收敛仿真算法,理解硬件信号逐级传递、稳态收敛的底层原理,建立了软硬件结合的思维模式。
程序调试、边界测试与排错能力大幅提升
在数十组异常测试用例的踩坑过程中,学会通过打印日志、查看映射表数据、分步断点调试定位隐性 bug。针对空指针、多源驱动、命名串扰、死循环等高频问题,总结出前置校验、数据隔离、算法优化的完整解决方案,不再只关注程序 “跑通正常用例”,而是主动思考各类边界、非法输入场景,养成工程开发的鲁棒性思维。读懂了 SourceMonitor 代码质量报表,学会通过圈复杂度、方法长度量化评估代码优劣,不再只追求功能实现,同步兼顾代码质量。
复杂分层项目的架构把控能力
作业 6 的双层电路架构(顶层电路 + 嵌套子电路),让我学会分层设计思想,把整体系统拆分为解析层、器件层、仿真调度层、输出层,每层职责清晰。子电路定义与实例化分离的设计模式,让我了解模块化复用的工程思路,面对复杂需求能够拆解为多个子模块逐个实现,再整合联调,具备了中小型项目的架构设计能力。
(二)现存不足与后续学习方向
算法能力有待补强
本次仿真采用暴力全量循环迭代,虽然可以完成功能,但电路规模扩大后性能短板明显。后续需要深入学习拓扑排序算法、图论有向无环图(DAG)相关知识,用拓扑排序优化求值流程,吃透图结构在电路拓扑中的应用;同时加强数据结构与算法练习,提升算法优化思维,而不是仅依靠暴力逻辑实现功能。
设计模式的运用不够熟练
本次仅用到基础的接口多态,工厂模式、单例模式、模板方法模式等常用设计模式仅停留在理论认知,没有主动融入项目。后续系统学习常用 23 种设计模式,结合本次电路项目进行改造优化,理解每种模式的适用场景,把设计模式真正用于架构优化,而非死记概念。
时序逻辑、硬件底层知识储备不足
当前项目仅覆盖组合逻辑电路,无法处理时钟、触发器等时序器件,对于时序电路的时序约束、建立保持时间、时钟调度等知识了解浅薄。后续同步学习时序数字电路知识,尝试在现有项目中新增时序仿真模块,拓展程序的适用范围;同时了解硬件 Verilog 基础,对比硬件描述语言与 Java 软件仿真的异同,加深软硬件底层认知。
工程化开发规范仍需加强
本次代码注释偏少,异常体系简陋,没有单元测试、版本管理流程。后续学习 JUnit 单元测试,为每一个器件、每一个工具方法编写单元测试用例,保证修改代码后功能回归正常;学习 Git 版本控制,规范代码迭代版本;学习日志框架、异常体系设计,向工业级工程代码规范靠拢。
(三)整体感悟
本次三次电路仿真作业,不是简单的 Java 编码练习,是软件面向对象思想与数字硬件逻辑的综合性实战。前期的架构设计直接决定后期迭代的工作量,前期偷懒写耦合代码,后期重构需要耗费数倍时间,让我养成了 “先架构、后编码” 的开发习惯。编程的本质是用代码复刻现实业务逻辑,硬件电路的规则严谨、边界明确,倒逼代码逻辑必须严密周全,任何一处逻辑漏洞都会在边界测试中暴露。后续我会补齐算法、设计模式、硬件底层知识短板,在现有项目基础上持续优化迭代,把本次项目作为学习的起点,不断打磨代码能力与工程思维。
浙公网安备 33010602011771号