代码改变世界

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

2026-06-24 20:11  BaoDatura  阅读(8)  评论(0)    收藏  举报

数字电路模拟程序迭代开发总结与复盘

前言

本阶段三次作业(作业集4~6)围绕"数字电路模拟程序"这一主题,从基础逻辑门实现逐步迭代,最终构建了一个支持子电路定义和异常检测的完整电路模拟系统。三次作业的知识点覆盖了数字电路基础理论、面向对象设计原则、组合设计模式的应用,以及编译原理中词法/语法分析的基础思想。题量上,从第一次的约200行核心代码,到第三次扩展到超过500行(含异常处理和子电路支持),代码规模呈线性增长。难度方面,每次迭代都在前一次基础上增加了新的概念维度——第一次重在理解电路计算模型,第二次引入了多引脚元件的建模挑战,第三次则上升到了系统健壮性和可扩展性的设计层面。

知识点覆盖

1.面向对象设计原则:特别是单一职责原则(SRP)和组合模式的应用

2.数字电路基础:逻辑门真值表、信号传播模型、组合逻辑电路

3.数据结构与算法:图遍历(信号传播)、拓扑排序、队列BFS

4.编译原理基础:词法分析、语法解析、异常优先级处理

5.Java核心技术:正则表达式、集合框架、多态与继承

题量与难度

作业

新增类数

核心代码行数

难度

核心挑战

第一次

2

~200行

中等

五种基本门电路建模、信号传播算法

第二次

1(重构)

~350行

中等偏上

四种组合元件扩展、多引脚管理

第三次

2

~500+行

较大

子电路递归解析、五种异常检测与优先级

二、设计与分析

1. 第一次作业:基础逻辑门电路模拟

(1)作业要求

实现数字电路的基础模拟程序。电路中包含与门(A)、或门(O)、非门(N)、异或门(X)、同或门(Y)五种基本元件。程序需要解析电路连接信息,根据输入信号计算各元件的输出电平,并按照A、O、N、X、Y的顺序输出所有可计算元件的引脚电平。

输入格式包括:

INPUT: A-1 B-0:外部输入信号

[A A(2)1-1]:连接信息,表示输出引脚A连接到与门A(2)1的1号引脚

end:输入结束标志

(2)代码规模复杂度与类图

屏幕截图 2026-06-24 183208

屏幕截图 2026-06-24 190659

(3)核心设计思路

Gate类设计:每个逻辑门封装为一个Gate对象,包含类型、输入引脚数、编号、输入引脚映射(引脚号→信号源)、输出值和计算状态。引脚映射使用Map<Integer, String>结构,便于快速查找和更新。

信号传播算法:采用迭代收敛法——反复扫描所有门电路,当某门的所有输入引脚都有确定值时,根据门类型计算其输出,并更新全局信号表。循环直至没有新的门可被计算为止。这种算法能正确处理组合逻辑电路中的任意依赖关系。

五种门计算逻辑

与门(A):所有输入为1时输出1,否则输出0

或门(O):任一输入为1时输出1,否则输出0

非门(N):输出为输入的取反

异或门(X):两输入不同时输出1,相同时输出0

同或门(Y):两输入相同时输出1,不同时输出0

排序输出:按照A→O→N→X→Y的顺序输出,同类按编号升序排列。对于未计算的门(输入不全),直接忽略不输出。

(4)踩坑心得

坑1:输入引脚未全部连接时的处理

最初实现中,当某门的输入引脚未全部连接时,直接跳过该门不计算。但实际测试中发现,有些门虽然部分引脚未连接,但仍应等待后续信号传播后再尝试计算。解决方案:在每次信号更新后重新扫描所有未计算的门,而不是只扫描一次。

坑2:正则表达式解析的边界情况

对于A(8)1这种带参数的门名,正则表达式需要同时匹配A(8)1和X5两种格式。最初使用([A-Z])(\d+)无法匹配带括号的情况,改为([A-Z])(?:\((\d+)\))?(\d+)后解决。

坑3:输出排序顺序

题目要求按A、O、N、X、Y顺序输出,而非按字母表顺序。需要自定义比较器实现特定排序。

(5)改进建议

将解析逻辑从Main中分离,形成独立的Parser类,提高可维护性。

引入工厂模式创建Gate对象,便于后续扩展新元件类型。

信号传播可改用队列BFS实现,避免重复扫描所有门。

2. 第二次作业:组合逻辑元件扩展

(1)作业要求

在第一次作业基础上,新增三态门(S)、译码器(M)、数据选择器(Z)、数据分配器(F)四种元件。这些元件具有多个引脚类型(控制引脚、输入引脚、输出引脚),引脚编号规则各不相同。

引脚排序规则:

含控制引脚的元件按控制→输入→输出顺序排列

每种类型引脚按编号从小到大排序

三态门:0→控制端,1→输入端,2→输出端

译码器M(3)1:0/1/2→控制引脚S1/S2/S3,3/4/5→输入引脚A0/A1/A2,6~13→输出引脚Y0~Y7

(2)代码规模复杂度与类图

屏幕截图 2026-06-24 183509

屏幕截图 2026-06-24 190712

(3)核心设计思路

Component抽象化:将第一次的Gate类重构为更通用的Component类,统一管理所有元件的引脚列表。每种元件的引脚分配在ensureComponent()方法中完成。

四种新元件的计算逻辑

三态门(S)

if (控制引脚 == 0) → invalid = true (高阻态,忽略输出)else → 输出 = 输入值

译码器(M)

if (S1==1 && S2==0 && S3==0) { // 正常工作 addr = A0 + A1*2 + A2*4 + ... 所有输出引脚置1,第addr个置0} else { // 无效状态 invalid = true}

数据选择器(Z)

sel = 控制位组合值 (二进制转十进制)输出 = 第(控制数 + sel)个数据输入引脚的值

数据分配器(F)

sel = 控制位组合值第sel个输出引脚 = 数据输入引脚的值其他输出引脚保持无效(不赋值)

信号传播改进:采用队列BFS(广度优先搜索)代替迭代扫描。从外部输入引脚开始,将信号值放入队列,依次传播到所有连接的目标引脚。当某个元件的所有输入引脚就绪时,立即计算其输出并加入队列。

(4)踩坑心得

坑1:译码器控制引脚的顺序

题目要求M(3)1的0/1/2对应S1/S2/S3,控制条件为S1=1且S2+S3=0。最初将S1误判为最低位,导致控制条件判断错误。经仔细对照题目描述后修正。

坑2:引脚编号的0号歧义

所有元件的输出引脚号默认为0,但输入引脚从1开始编号。在解析A(2)1-0时,需要识别-0为输出引脚而非输入引脚。解决方案:在解析时显式判断pinNo == 0的情况。

坑3:数据选择器地址位顺序

地址位顺序为:最低位对应第一个控制引脚。例如Z(2)1中,S1S0=10时选择D2。需要正确地将控制位组合转换为十进制索引。

(5)改进建议

引脚分配逻辑过于集中,可拆分为独立的PinAllocator类。

每种元件类型的计算逻辑可提取为独立的策略类,便于单元测试。

引入枚举类型定义元件种类,避免字符串散落各处。

3. 第三次作业:子电路与异常检测

(1)作业要求

在第二次作业基础上新增两大功能:

子电路(SubCircuit)

使用C编号:定义子电路,endc结束

子电路内部包含INPUT、OUT和连接信息

主电路通过C编号-端口名引用子电路

子电路元件输出时带上子电路编号:C编号-元件名-引脚号:值

异常检测(五种)

优先级

异常类型

示例

输出

1

多个输出

[A B A2-1]

ERROR: [A B A2-1] include more than one input

2

无输入

[C1-1 C2-1]

ERROR: [C1-1 C2-1] include none input

3

无输出

[A]

ERROR: [A] include none output

4

顺序错误

[A2-1 A]

ERROR: [A2-1 A] input and output sequence error

5

信号冲突

[A A2-1] + [B A2-1]

ERROR: A2-1 input signal conflict

(2)代码规模复杂度与类图

屏幕截图 2026-06-24 184039

屏幕截图 2026-06-24 190734

(3)核心设计思路

组合模式应用:将SubCircuit和Gate统一视为Component的子类,实现"部分-整体"的一致性处理。MainCircuit作为顶层容器,管理所有顶层组件和全局输入。

子电路解析流程

1. 扫描输入,遇到"C编号:"开始子电路定义2. 解析INPUT行 → 创建输入端口3. 解析OUT行 → 创建输出端口4. 解析连接信息 → 构建子电路内部网络(使用SubCircuit作为上下文)5. 遇到"endc"结束定义6. 主电路中"C编号-端口名" → 查找子电路的输入/输出端口

异常检测实现策略

分两个阶段:

语法级检测(解析时):检测"多个输出"、"无输入"、"无输出"、"顺序错误"

语义级检测(构建后):检测"信号冲突"

优先级通过检测顺序控制:先检测优先级1→2→3→4,最后检测优先级5。一旦检测到异常,立即输出并终止程序。

信号传播增强

1. 设置全局输入信号2. 将输入引脚加入传播队列3. BFS传播:取出队列头部,遍历其所有targets4. 对每个target,若其所属组件的所有输入已就绪,计算组件输出5. 将新计算的输出引脚加入队列6. 子电路的输入端口获得信号后,继续传播到内部元件7. 子电路的输出端口获得信号后,传播到主电路

(4)踩坑心得

坑1:子电路内部"输出"的语义反转

在子电路内部,OUT声明的端口对于内部元件来说是"目标引脚"(输入方向),但对于外部主电路来说是"信号来源"(输出方向)。这个语义反转在处理连接时需要特别小心。

坑2:异常"多个输出"的含义理解

题目中的"一个连接信息中包含两个或多个输入"指的是连接系统的输出(即等号左边的引脚),而不是常规意义的输入。例如[A(2)1-0 O(2)1-0 A(2)1-1]中,A(2)1-0和O(2)1-0都是输出引脚,出现了两个输出,因此触发异常1。这个语义反转在一开始造成了理解偏差。

坑3:异常优先级与多异常同存的处理

当一条连接信息同时存在多种异常时,需要按优先级输出最高的那一种。例如既有"多个输出"又有"顺序错误",应输出"多个输出"。我的实现中使用了"检测到即返回"的策略,但需要确保优先级顺序与检测顺序一致。

坑4:子电路输出格式

输出时需要带上子电路编号:C1-A(2)1-0:0。这个格式化要求需要在输出收集时额外记录子电路ID。

(5)改进建议

异常检测可独立为ExceptionHandler类,与解析逻辑解耦。

子电路的端口方向管理可引入"内部视角/外部视角"两种视图。

信号传播可加入循环检测机制,防止未来引入时序元件时出现死循环。

引入日志系统,便于调试复杂电路。

三、总结

1. 知识点收获

面向对象设计

深刻理解了单一职责原则(SRP)的重要性——第一次作业中Main承担了太多职责,第二次重构后每个类的职责更加清晰;

学会了组合模式的应用——将子电路和基本元件统一为Component,实现了"部分-整体"的一致性处理;

理解了组合与聚合的区别——Gate与Pin是组合关系(同生共死),Component与List<Component>是聚合关系(可独立存在)。

数据结构与算法

队列BFS在信号传播中的应用,比迭代收敛法效率更高

图的拓扑排序思想在电路依赖分析中的应用

正则表达式在文本解析中的灵活运用

软件工程实践

异常处理的优先级设计

分阶段解析(词法→语法→语义)的架构思想

可扩展性设计的重要性——第二次和第三次的扩展都要求对现有代码进行修改,说明初始设计不够开放

2. 不足与改进方向

设计层面

前期设计不够充分,导致第二次和第三次需要对第一次代码进行较大重构

对"开闭原则"的理解停留在理论层面,实际编码中仍习惯使用if-else分支

编码习惯

存在"重编码轻设计"的问题,拿到题目后急于写代码,类图设计不够细致

变量命名有时不够语义化,如param、kind等缩写降低了代码可读性

测试意识

测试依赖题目提供的样例,缺乏主动设计测试用例的意识

没有使用单元测试框架,手动测试效率低下且覆盖不全

改进方向

养成"设计优先"的习惯,编码前先画出类图、明确类间关系

学习使用JUnit等单元测试框架,建立自动化测试思维

阅读优秀开源项目的源码,学习其中"开闭原则"的实现方式

遇到格式解析类问题,优先考虑分层解析而非一次性匹配

3. 对课程的建议

作业设计

三次作业循序渐进,难度梯度合理,从基础逻辑门到组合电路到异常处理,符合软件开发的迭代规律

子电路的引入使系统复杂度跃升,建议提供更多的子电路示例和边界测试用例

SRP要求

严格遵循单一职责原则有助于培养良好的设计习惯,建议在后续作业中继续坚持这一要求

建议在评分标准中增加"设计合理性"的权重,不仅看功能是否实现,更要看代码结构是否优雅

测试指导

建议提供更多的测试用例供学生参考,特别是边界值用例

可以介绍自动化测试的基本方法,鼓励学生编写单元测试

代码互查

建议增加代码互查环节,同学之间互相学习、互相发现问题

互查可以聚焦于设计模式的应用和代码规范,而不仅是功能正确性