数字电路模拟程序设计——三次作业集总结报告

一、前言
本阶段三次作业集(数字电路模拟程序-1、-2、-4)是一个循序渐进的系列编程实践,旨在通过模拟数字电路的行为,训练面向对象设计与分析能力,同时深入理解电路原理与软件工程的结合。

1.1 知识点分布
第一次作业的核心知识点包括基本逻辑门(与、或、非、异或、同或)的模拟、引脚连接关系的解析、信号在电路网络中的传播以及拓扑排序算法的应用。第二次作业在此基础上新增了复合元件(三态门、译码器、数据选择器、数据分配器),引入了控制引脚的概念,并需要处理多输出元件的格式化输出和无效状态。第三次作业则聚焦于子电路定义与引用、异常检测与处理,要求运用组合模式进行系统设计。

1.2 题量与难度
每次作业均为1道大题,但内部包含多个测试点(第一次6个,第二次10个,第三次8个),整体代码量逐次递增。难度呈阶梯式上升:第一次以基础门电路模拟为主,重在数据结构和拓扑排序;第二次引入了多引脚复合元件,需重新设计引脚管理体系;第三次增加了子电路和异常处理,对系统健壮性提出了更高要求。

1.3 个人完成情况
三次作业均成功通过全部测试点,代码总行数从第一次的约400行扩展至第三次的约1200行,经历了从"面向过程"到"面向对象"的转变过程。

二、设计与分析
2.1 第一次作业——基础门电路模拟
第一次作业的核心任务是模拟五种基本逻辑门,输入连接关系后计算各元件输出。我采用了"输入解析→依赖图构建→拓扑排序→信号传播"的四阶段处理流程。

在数据结构设计上,我使用Map存储元件信息、输入信号、连接关系和输出结果。Electricity类包含了元件名、类型、输入引脚数、编号、输入值列表、输出值和计算状态等属性。这种设计能够满足基本需求,但也存在明显的结构性问题。

从SourceMonitor分析报告来看,第一次作业总代码行数为387行,注释率仅8.2%,最大方法复杂度达到12,平均方法复杂度为4.3,类数量只有2个。这些数据反映出代码虽然能通过测试,但所有逻辑集中在Main类的静态方法中,缺乏对象封装;元件类型判断使用大量if-else分支,扩展性差;信号传播采用递归方式,存在栈溢出风险。

在类图设计上,Main类承担了所有职责,包括输入解析、信号传播和门电路计算,而Electricity类仅仅作为数据容器。这种设计违背了单一职责原则,为后续扩展埋下了隐患。

plantuml-diagram

2.2 第二次作业——复合元件扩展
第二次作业新增了五种复合元件,最大的挑战在于引脚类型的区分——控制引脚、输入引脚、输出引脚各有不同的编号规则和行为逻辑。

我引入了PinStruct类来管理元件的三类引脚。不同元件的引脚映射规则各不相同:三态门的0号引脚为控制端、1号为输入端、2号为输出端;译码器的0至2号引脚为控制端,后续为输入端和输出端;数据选择器和数据分配器的控制引脚数量由参数决定。

从SourceMonitor分析报告来看,第二次作业总代码行数为682行,注释率提升至12.5%,最大方法复杂度为18,平均方法复杂度为6.1,类数量增至6个。这些数据表明我在第二次作业中初步尝试了面向对象设计,将引脚解析、值计算、输出格式化分别封装到不同方法,并使用Map管理同类型元件编号。但继承体系的缺失仍然是一个突出问题——所有元件共用一套计算逻辑,通过switch-case语句区分类型。

在类图设计上,Main类依然承担了主要控制逻辑,但引入了CompInfo类存储元件基本信息,PinStruct类管理引脚分类。这种设计比第一次有所进步,但元件计算逻辑仍然集中,缺乏对多态性的运用。

plantuml-diagram (1)

2.3 第三次作业——子电路与异常处理
第三次作业的核心要求是实现子电路功能并增加异常检测。这是对前两次作业的一次系统性重构——我采用了组合模式,将基本元件和子电路统一对待。

在组合模式的设计中,CircuitComponent作为抽象组件定义了统一的接口,LogicGate作为叶子节点实现了基本门电路的计算逻辑,SubCircuit作为复合节点包含了子电路内部的元件列表、输入输出映射关系,并实现了递归计算。这种设计使得主电路可以像对待普通元件一样对待子电路,极大地简化了系统架构。

异常处理方面,我定义了六种异常类型并按优先级排序:多个输入异常优先级最高,依次为无输入异常、无输出异常、顺序错误异常、信号冲突异常。异常检测按照优先级顺序逐一检查,一旦发现异常立即报告并终止后续处理。

从SourceMonitor分析报告来看,第三次作业总代码行数为1186行,注释率提升至18.7%,最大方法复杂度为22,平均方法复杂度为7.8,类数量增至11个。复杂度上升反映了功能增强的客观需求,但通过合理的类结构设计,代码的可维护性并未下降。

在类图设计上,组合模式的引入使得系统结构发生了根本性变化。LogicGate和SubCircuit都实现了CircuitComponent接口,Main类不再直接操作具体元件,而是通过接口与所有组件交互。ErrorHandler类独立负责异常检测,遵循了单一职责原则。

plantuml-diagram (2)

三、采坑心得
3.1 第一次作业——拓扑排序的坑
初始实现中,我采用递归方式传播信号,遇到包含反馈环的连接时出现死循环。例如信号从A传到与门,与门输出又通过其他路径传回A,形成了循环依赖。我最初没有检测环路的机制,导致程序陷入无限递归而崩溃。

解决方案是改用Kahn拓扑排序算法。我首先计算所有节点的入度,将入度为零的节点入队,然后逐层处理:从队列取出节点计算其输出值,将该输出连接的所有输入节点的入度减一,如果某个输入节点的入度变为零则将其入队。这样既能正确处理依赖关系,又能检测出环路——如果处理完成后还有节点未被处理,说明存在环。

3.2 第一次作业——引脚编号解析的坑
引脚信息的解析看似简单,实则暗藏陷阱。元件名如A(8)1-2中,元件名部分为A(8)1,引脚号为2。我最初尝试用简单的字符串分割方法,但在后续扩展中遇到了麻烦——当元件名包含多个括号和数字时,简单的分割无法正确提取信息。

最终我采用正则表达式进行统一解析。针对带参数元件如A(8)1和不带参数元件如X5,分别设计了匹配模式,能够准确提取元件类型、参数和编号。这个经验告诉我,对于格式复杂的输入,正则表达式是最可靠的工具。

3.3 第二次作业——三态门输出无效状态
三态门控制端为0时输出为"无效状态",但第一次作业中所有输出都是确定的0或1。我最初将无效状态表示为-1,但在输出时发现题目要求是忽略该元件而非显示-1。

经过调试,我改为在pinValue中不存储无效引脚的值,在输出阶段检查引脚是否存在,如果不存在则跳过该元件的输出。这个修改看似简单,但涉及到整个信号传播链路的调整——所有依赖该输出的上游元件也需要相应处理。

3.4 第二次作业——译码器输出格式
译码器的输出格式比较特殊,需要输出"输出为0的引脚编号",如M(2)1:0表示Y0输出0而其他输出为1。我最初尝试输出所有引脚的值,但格式不符合要求,导致测试点失败。

正确逻辑是遍历译码器的所有输出引脚,找到第一个值为0的引脚,输出其编号即可。这是因为译码器在正常工作状态下只有一个输出为0,其余都为1。如果所有输出都不是0,说明译码器处于无效状态,应被忽略。

3.5 第三次作业——子电路递归展开
子电路可以嵌套引用其他子电路,且子电路中的元件编号可能与主电路重复。我最初的实现是扁平化处理,将所有子电路展开后统一计算,但这样丢失了子电路的命名空间信息,导致不同子电路中同名元件被混淆。

解决方案是为每个子电路维护独立的命名空间。每个SubCircuit对象内部使用独立的Map存储元件,输出时在元件名前添加子电路编号前缀。这样即使两个子电路都有名为N1的非门,在主电路中也能通过C1-N1和C2-N1区分。

3.6 第三次作业——异常优先级处理
题目规定"如果一条输入出现了多种异常,按异常先后顺序为优先级",多条连接信息中"只处理排在最前面的异常信息"。我最初的实现是遍历所有连接,分别检查所有异常,结果可能报告后面的异常而忽略了更前面的。

修正后采用"首次发现即停止"策略:按输入顺序遍历连接信息,对每个连接依次检查各种异常,一旦发现异常立即输出并终止整个处理流程。这样既保证了优先级顺序,也保证了连接顺序的正确性。

四、改进建议
4.1 设计模式层面
当前所有元件共用一个计算类,通过switch-case区分类型,这种方式在元件种类增多时会导致代码膨胀。建议引入策略模式,每种元件独立实现计算策略,这样新增元件类型时无需修改已有代码,符合开闭原则。

引脚值存储为Map<String, Integer>,对引脚的操作散落在各处。建议采用访问者模式遍历电路图,便于添加新的操作如打印、验证等,同时将数据结构与操作分离。

异常检测散落在主流程中,与业务逻辑耦合。建议建立责任链模式处理异常,将各种异常检测封装为独立的处理器对象,按优先级组成链式结构,这样既清晰管理了异常优先级,又易于扩展新的异常类型。

4.2 代码质量层面
建议引入单元测试框架,为每个元件类型编写独立测试用例。例如为与门编写测试,验证不同输入组合下的输出是否正确;为译码器编写测试,验证控制端有效和无效时的行为差异。单元测试不仅能保证代码质量,还能在重构时提供安全网。

将硬编码的引脚编号规则抽取为配置文件或枚举。不同元件的引脚映射规则目前散落在代码中,如果规则发生变化需要修改多处代码。可以定义PinConfig类来描述每种元件的控制引脚数、输入引脚数和输出引脚数,通过配置文件进行管理。

增加日志记录便于调试复杂电路。在计算过程中记录每个元件的输入值和输出值,当出现错误时可以快速定位问题元件。日志级别可以动态调整,在开发阶段输出详细日志,在正式运行时只输出必要信息。

4.3 功能扩展层面
后续作业将涉及时序电路如D触发器和JK触发器,这些元件需要引入时钟信号和状态存储。建议提前设计状态管理机制,为元件增加内部状态属性,支持在时钟边沿更新状态。

波形输出功能可以支持多时间点采样,输出信号随时间变化的波形图。这需要记录每个时间点的信号值,并在模拟结束后以图形化方式展示。

图形化界面可以将电路图可视化,支持拖拽连线,极大提升用户体验。可以考虑使用JavaFX或Swing实现简单的图形界面。

网表导入导出功能支持标准EDIF格式,可以与其他EDA工具交互,使程序具备实际应用价值。

五、总结
5.1 学习成果
通过三次迭代式的作业实践,我在面向对象设计能力方面获得了显著提升。从第一次作业的面向过程风格,所有逻辑集中在Main类中,逐步演进到第三次作业的组合模式设计,深刻理解了封装、继承、多态三大特性在实际项目中的运用,掌握了组合模式在处理"部分-整体"层次结构中的应用场景。

在数据结构与算法应用方面,图论算法中的拓扑排序在信号传播中的实际应用让我加深了对Kahn算法的理解,子电路嵌套的递归展开让我对树的遍历有了更深刻的认识,哈希表在管理大量元件和引脚信息时展现出了高效性。

在代码质量意识方面,从387行膨胀到1186行,但通过合理拆分,可维护性反而提升。我理解了单一职责原则的重要性,学会了使用SourceMonitor等工具量化分析代码质量。

在问题分析与调试能力方面,面对复杂测试用例,能够系统性地定位问题,养成了先画图后编码的习惯。

5.2 不足之处与后续学习方向
我对设计模式的理解仍停留在表面,需要深入学习《设计模式:可复用面向对象软件的基础》,重点学习访问者模式、观察者模式等在复杂系统中的应用。

目前缺乏单元测试覆盖,需要学习JUnit和测试驱动开发方法论,建立测试优先的开发习惯。

异常处理不够优雅,需要研究Java的异常体系,建立统一的异常处理框架,区分可恢复异常和不可恢复异常。

对时序电路尚不熟悉,需要补充数字电路基础知识,学习时序逻辑分析方法,理解建立时间、保持时间等概念。

5.3 心得体会
这三次作业让我深刻体会到软件工程不是一次性编码,而是持续演进的过程。第一次作业勉强能跑通时,我以为已经完成了;但当第二次、第三次需求到来时,才发现最初的架构根本经不起扩展。这迫使我不断重构,最终走向了组合模式的设计。

另一个重要感悟是好的设计能让复杂问题变得简单。第三次作业中,子电路和异常处理看似增加了大量复杂度,但通过合理的类结构设计,反而让代码更加清晰。

最后,引用《Clean Code》中的一句话作为总结:代码是写给人看的,只是顺便能在机器上运行。在追求功能实现的同时,代码的可读性、可维护性同样重要,这是我在本次作业中收获的最宝贵经验。

posted @ 2026-06-24 19:15  sqoop  阅读(2)  评论(0)    收藏  举报