Java数字电路模拟程序迭代开发总结

一、前言

本阶段OOP题集4~6围绕“数字电路模拟程序”展开,从基础逻辑门仿真逐步扩展到多输入输出元件、子电路和异常检测。与之前航空货运配载系统偏向数据管理不同,本次作业的核心是信号传播仿真,通过元件接收输入信号、计算输出、再将信号传递给下游元件,形成一个完整的信号流网络。

三次作业的知识点覆盖:

第一次作业:基础逻辑门(与门、或门、非门、异或门、同或门)的建模、链表/数组管理元件、选择排序、广度优先信号传播。

第二次作业:新增三态门、译码器、数据选择器、数据分配器,引脚类型扩展(控制引脚、多输出引脚),工厂模式创建元件,插入排序。

第三次作业:子电路定义与展平、异常输入检测(多源冲突、无源无汇、顺序错误等),组合模式思想的应用。

题量与难度:

第一次作业代码有473行,类数量14个,难度中等。主要难点在于理解信号传播的“队列驱动”机制。

第二次作业代码有716行,类数量17个,难度较高。新增元件种类多,引脚编号规则复杂,需要仔细处理每种元件的计算逻辑。

第三次作业代码有723行,类数量20个,难度最高。子电路的递归展平和异常检测的优先级处理是最核心的挑战。这也是三个作业中我唯一未达满分的作业,共有5个测试点未能通过(1个单子电路、4个多子电路),暴露了子电路展平逻辑中端口映射和信号传播的缺陷。

三次作业从“单层逻辑门”到“含控制引脚的复杂元件”再到“子电路嵌套+异常处理”,是一个增量式迭代开发过程。

二、设计与分析

以下分析将同上一次一样,使用SourceMontor生成的报表内容plantuml生成的相应类图。我将从代码的基本情况,完成时出现的问题和改进等方面进行分析。

第一次作业

生成报表

屏幕截图 2026-06-22 221223

从数据可以看出,总行数为473行,语句数312,类数量达到14个,说明已经将不同职责拆分到多个类中,符合单一职责原则的雏形。
每类平均方法数2.43,每方法平均语句数7.24,方法拆分尚可。最大圈复杂度为26,出现在Sim.run()方法中,该方法同时承担了队列管理、信号分发、元件查找等多个职责,导致复杂度偏高,是本次作业中最复杂的模块。

类图结构

屏幕截图 2026-06-22 214243

本次作业我将所有元件统一为Component抽象类,每个元件拥有inSign[]数组存储各输入引脚的电平(0=未连接,-1=低电平,1=高电平)。

Factory类负责根据元件名称字符串(如"A(8)1"、"X5")解析出类型、参数和编号,创建对应的元件实例。

Parser类将输入的文本行解析为Data对象,包含输入信号、元件列表和连接规则。

Data类作为数据容器,存储输入信号、元件列表、连接规则等信息。

Sim类采用队列驱动方式进行信号传播。初始输入信号入队,每次从队首取出一个信号,遍历所有以该信号为源的连接规则,将信号值写入目标元件的对应引脚,若该元件所有输入均已就绪则调用compute()计算输出,再将输出信号入队继续传播。

Result类负责对计算完成的元件按类型和编号排序输出。

Agate、Ogate、Ngate、Xgate、Ygate类分别继承Component,实现与门、或门、非门、异或门、同或门的compute()计算逻辑。

Tool类提供工具方法,如字符串分割、数字判断等。

出现过的问题

1.基础门电路的输入引脚从1开始编号(如A(8)1-1表示1号引脚),但输出引脚却为0号(如X5-0)。所以我在初始化inSign[]数组时,为旧门电路分配了inNum+1长度,索引0闲置,1~inNum对应实际引脚。这个设计虽然工作正常,但在后续扩展中需要记得区分新旧元件的引脚编号规则。

2.最初实现Sim.run()时没有维护visited集合,导致同一个输出信号可能被多次入队,造成无限循环。解决方法是用一个字符串数组记录所有已入队的信号名称,入队前检查是否已存在。

心得

第一次作业让我理解了"队列驱动"的信号传播机制在仿真系统中的应用。每个信号只需入队一次,通过visited集合去重,避免了无限循环。

Component.change()方法中先检查ifConnect()再调用compute(),确保了只有所有输入引脚都收到信号后才计算输出,这让我对"事件驱动"的编程思想有了初步认识。

能改进的方面

1.拆分Sim.run()方法:当前Sim.run()承担了队列管理、信号分发、元件计算触发等多个职责,超过80行。可拆分成多个子方法,降低圈复杂度。

2.使用Map替代线性查找:findComp()每次都遍历整个数组查找元件,在元件数量较多时效率低。可使用Map<String, Component>实现O(1)查找。

第二次作业

生成报表

屏幕截图 2026-06-22 221112

从数据可以看出,总行数达到716行,语句数657,代码规模明显扩大。类数量从14增加到17,每类平均方法数从2.43上升到4.24,说明类的职责更加丰富。
最大圈复杂度从26降至23,Analyze.parse()虽然逻辑复杂但拆分相对合理。分支数比例29.1%,说明代码中有较多条件判断,这主要源于新增元件种类的多样性。平均圈复杂度2.67,整体可控。

类图结构

屏幕截图 2026-06-22 214718

本次作业在第一次的基础上扩展了Component类,增加了outNum、outVals[]和outStartPin字段以支持多输出元件。

Sgate类继承Component,实现三态门。包含控制端、输入端和输出端,控制端为高电平时导通,低电平时输出无效状态。

Mgate类继承Component,实现译码器。包含控制引脚、输入引脚和多个输出引脚,控制端满足条件时根据输入编码选通一路输出为低电平。

Zgate类继承Component,实现数据选择器。包含控制端和多个数据输入端,根据控制端编码从多路输入中选择一路送至输出端。

Fgate类继承Component,实现数据分配器。包含控制端、一个数据输入端和多个输出端,根据控制端编码将输入信号送至对应的输出端。

Factory类被扩展以支持9种元件的创建。

Sim类被升级以支持多输出。change()后可能产生多个输出信号,需要全部入队。
Analyze类负责解析输入并构建数据。

Result类使用插入排序对元件按类型和编号排序,最后调用各元件的formatOutput()方法输出结果。

出现过的问题

1.三态门的引脚定义为:0号控制端、1号输入端、2号输出端。我最初按照旧门电路的逻辑,认为输入引脚从1开始,导致控制端信号被写入错误位置。仔细阅读题目后,将新元件的inSign[]索引改为从0开始,与引脚号直接对应。

2.题目要求译码器输出格式为"M(3)1:3"(表示Y3输出0,其余为1),而不是像普通门那样输出"M(3)1-0:1"。我需要在Mgate.formatOutput()中特殊处理,找到值为-1的输出引脚编号并输出。

心得

第二次作业让我理解了工厂模式在实际开发中的应用价值,Factory.make()根据元件名称字符串解析出类型、参数和编号,创建对应的元件实例。这种设计使得添加新元件类型时只需修改工厂类和新增元件子类,符合开闭原则。

同时,多输出支持的加入使仿真器更加通用,为后续子电路的加入奠定了基础。

插入排序替代了第一次的选择排序,虽然时间复杂度仍为O(n²),但代码更简洁。

不同元件的formatOutput()各有差异,通过多态实现了统一的输出接口,这也是面向对象编程的优势所在。

能改进的方面

1.统一新旧元件的引脚编号规则:旧门电路输入从1开始,新元件输入从0开始,这种不一致容易引发bug,可以统一为从0开始.

2.使用策略模式处理不同元件的计算逻辑:当前每个元件子类重写compute(),但compute()中包含了引脚映射和业务逻辑的混合。可以将引脚映射提取到独立的类中。

第三次作业

生成报表

屏幕截图 2026-06-22 221413

从数据可以看出,总行数达到723行,语句数677,类数量达到20个,系统规模已相当可观。最大圈复杂度升至28,Analyze.flattenAndBuild()承担了子电路展平和主电路构建的双重职责,逻辑复杂,是本次作业中最复杂的模块。每方法平均语句数5.96,比第二次略有下降,说明方法拆分更细,但核心方法的圈复杂度依然很高。

类图结构

屏幕截图 2026-06-22 215104

本次作业在第二次的基础上新增了SubCircuit类,用于存储子电路的输入输出端口列表、内部连接和元件映射。

Data类增加了subMap字段存储所有子电路定义。

Analyze类进行了大幅扩展,核心变化包括:先遍历收集所有Cx:子电路定义块,再解析主电路。

flattenAndBuild()方法将子电路内部的连接规则和元件合并到主数据中,元件名前加上"C{id}-"前缀以区分不同子电路的同名元件。

detectAnomaly()方法检测连接信息中的五种异常并按优先级输出。

出现过的问题

1.题目要求"所有子电路信息都在主电路信息之前定义完成"。我的解析器先遍历一遍收集所有Cx:块,再从头开始解析主电路。但第二次遍历时跳过了子电路块,导致行索引管理出错。最终采用了"两阶段解析":先收集子电路,再解析主电路,跳过已处理的子电路区域。

2.两个不同的子电路可能都有名为X1的异或门。展平时需要为每个元件名加上子电路前缀,如C2-X1和C3-X1。我的addPrefixIfLocal()方法需要判断一个引脚名是"全局引用"(如C2-A)还是"局部名称"(如X1),前者不加前缀,后者加前缀。

心得

第三次作业让我深刻体会到"角色转换"在系统设计中的复杂性。
同一个端口在子电路内部和外部扮演的角色完全不同。子电路的输入端口在内部是信号源,在外部是信号目标;输出端口则正好相反。

异常检测的优先级设计也让我意识到,在复杂系统中,错误处理的"顺序"本身就是一种业务规则。
虽然第三次作业未能满分,但暴露出的逻辑缺陷恰恰是我最需要补强的方向。

能改进的方面

1.重构子电路展平逻辑:当前flattenAndBuild()方法超过150行,承担了子电路收集、异常检测、元件注册、规则构建等多个职责,可以拆分开来,使各种方法的职责更明确。

2.当前异常检测与展平逻辑混合在一起,增加了复杂度。可以在解析阶段完成异常检测和所有语法和语义检查,确认无误后再进行展平和仿真。

三、总结

通过这三次数字电路模拟程序的迭代作业,我在Java面向对象编程和系统设计方面获得了显著的提升。

学到的核心知识

1.队列驱动的信号传播机制是数字电路仿真的核心,每个信号只传播一次,元件在输入就绪后立即计算输出,这种"事件驱动"的思想在仿真系统中具有普适性。

2.Factory.make()根据字符串动态创建不同类型的元件,使得系统易于扩展,新增元件类型时只需修改工厂类和新增子类,符合开闭原则。

3.多种异常同时存在时按优先级输出,这种设计在编译器、解释器等系统中很常见。我因此学会了用"检查顺序"来控制优先级。

需要进一步学习的方向

1.子电路可以嵌套子电路,我目前的展平逻辑是单层展平,无法处理嵌套,需要学习如何递归处理树形结构。

2.三次作业的注释比例都不足,在团队协作中这样的代码可维护性很低,我正在逐渐努力养成良好的注释习惯。

对课程的建议

建议在作业发布时提供更多的示例输入输出,特别是针对边界条件和异常情况的示例,帮助我们更好地理解题目要求。

总之,这三次数字电路模拟程序作业让我从"面向对象编程"迈向了"面向对象设计"。从最初只是把逻辑塞进类里,到后来开始思考类与类之间应该如何协作、如何隔离变化、如何为扩展留有余地。这种思维方式上的转变,才是这三轮迭代最宝贵的收获。第三次作业虽未达满分,但暴露出的子电路展平逻辑缺陷,恰恰是我目前最需要补强的方向。有遗憾,但也因此有了更明确的发力点。

posted @ 2026-06-23 17:21  王杨博  阅读(4)  评论(0)    收藏  举报