数字电路模拟程序迭代开发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)代码规模复杂度与类图


(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)代码规模复杂度与类图


(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)代码规模复杂度与类图


(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要求:
严格遵循单一职责原则有助于培养良好的设计习惯,建议在后续作业中继续坚持这一要求
建议在评分标准中增加"设计合理性"的权重,不仅看功能是否实现,更要看代码结构是否优雅
测试指导:
建议提供更多的测试用例供学生参考,特别是边界值用例
可以介绍自动化测试的基本方法,鼓励学生编写单元测试
代码互查:
建议增加代码互查环节,同学之间互相学习、互相发现问题
互查可以聚焦于设计模式的应用和代码规范,而不仅是功能正确性
浙公网安备 33010602011771号