数字电路模拟程序三次迭代作业总结

数字电路模拟程序三次迭代作业总结
一、前言
本阶段三次PTA作业以“数字电路模拟程序”为主题,从基础逻辑门到复杂组合逻辑元件,再到子电路与异常处理,完成了三个版本的迭代开发。这三次作业不仅巩固了Java面向对象编程的基础,更让我初步接触了电路仿真、拓扑排序、组合模式等软件工程中的经典应用场景。

知识点覆盖:

程序1:面向对象基础、类与对象、逻辑门(与或非异或同或)、信号传播、拓扑排序、输入解析。

程序2:扩展元件(三态门、译码器、数据选择器、数据分配器)、控制引脚与多输出引脚处理、引脚类型分类。

程序4:子电路定义与引用、组合模式(Composite Pattern)、异常输入检测及优先级处理。

题量与难度:

程序1:约200行代码,5种元件,逻辑清晰,难度适中。

程序2:代码量增加至350行左右,元件种类增至9种,引脚类型多样,处理逻辑复杂,难度较高。

程序4:新增子电路和异常检测,代码量突破500行,设计模式应用成为关键,难度最大。

三次作业层层递进,从简单逻辑门的静态计算到含控制信号的多路选择,再到可复用的子电路设计和鲁棒性增强,完整模拟了数字电路设计从单元到系统的演进过程。

二、设计与分析
(一)数字电路模拟程序1(基础门电路)

  1. 需求简述
    实现与门、或门、非门、异或门、同或门五种基本逻辑门。输入为外部信号,通过连接关系驱动各门,按元件类型顺序(与、或、非、异或、同或)输出所有有效输出引脚的电平。约束:每个门只有一个输出引脚(编号0),输入引脚编号连续,一个输入引脚只能接一个信号源。

  2. 类设计分析
    我设计了以下核心类:

元件抽象类(LogicElement):包含元件名称、输入引脚列表、输出引脚值、类型标识等,提供计算输出的抽象方法。

具体门类:继承自LogicElement,重写compute()方法实现对应逻辑。

引脚类(Pin):记录引脚编号、信号值、是否已连接。

电路类(Circuit):管理所有元件和外部输入,解析连接关系,建立信号传播图。

拓扑排序器:根据连接关系确定元件计算顺序,避免循环依赖(本题无反馈,为有向无环图)。

类图(简化):

text
LogicElement (abstract)
├── AndGate
├── OrGate
├── NotGate
├── XorGate
└── XnorGate
Pin (组合于LogicElement)
Circuit (聚合LogicElement, 管理拓扑)
TopoSorter (依赖Circuit)
设计亮点:

使用工厂方法根据标识符创建具体门,易于扩展新元件。

输入解析采用正则表达式提取元件名和编号,容错性较好。

采用邻接表建立信号流向,使用Kahn算法进行拓扑排序。

复杂度分析:程序1代码行数约200,类数6,平均方法复杂度3.2。最大圈复杂度出现在输入解析方法(约6),整体可读性良好。

(二)数字电路模拟程序2(多引脚及控制元件)

  1. 新增需求
    新增三态门、译码器、数据选择器、数据分配器四种元件。这些元件拥有控制引脚、多个输入/输出引脚,输出可能为无效状态(高阻态或全部为1等)。输出要求:普通门输出引脚电平;译码器输出有效输出为0的引脚号;数据分配器按引脚顺序输出各输出端信号,无效用“-”表示。

  2. 类设计扩展
    新增元件类:继承LogicElement,重写compute方法,处理控制引脚的逻辑。

引脚类型区分:在Pin中增加pinType属性(INPUT, OUTPUT, CONTROL),并在解析时依据元件类型和引脚编号范围分类。

信号状态:增加UNKNOWN状态表示未连接或高阻态,输出时过滤。

关键设计决策:

译码器:控制引脚(S1,S2,S3)的有效条件为S1=1且S2+S3=0,输入引脚编码决定哪个输出引脚为0,其余为1(无效输出不输出)。

数据选择器:控制引脚数量决定选择路数,通过计算控制值索引到对应输入引脚。

三态门:控制=1时输出等于输入,否则输出为无效(不计入输出)。

类图变化:
新增TriStateGate, Decoder, Multiplexer, Demultiplexer,均继承LogicElement。电路解析时增加对“标识符(控制数)+编号”格式的支持。

  1. 排序与计算顺序
    由于存在组合逻辑,仍然采用拓扑排序,但需注意数据选择器的控制引脚信号必须先于其输入选择计算,因此排序时需正确建立依赖关系。我采用先收集所有输出引脚(包括元件输出和外部输入)作为节点,连接关系作为边,使用Kahn算法得到计算序列。

代码复杂度:程序2代码行数约350,类数10,平均方法复杂度4.1。新增元件的compute方法复杂度较高(译码器约8),但控制在可接受范围。

(三)数字电路模拟程序4(子电路与异常处理)

  1. 新增需求
    子电路:允许用户定义子电路(C编号:),包含内部元件、输入输出引脚,可在主电路中像元件一样实例化。子电路输出引脚命名格式为“子电路编号-元件名-引脚号”。

异常检测:对连接信息进行五类异常检查(多个输入、无输入、无输出、顺序错误、输入信号冲突),按优先级输出第一个异常。

  1. 设计模式应用:组合模式
    题目建议采用组合模式,将子电路视为一种组合元件,与普通元件统一处理。我实现了一个CompositeElement类,继承自LogicElement,内部包含一个Circuit实例(即子电路的完整电路),并拥有自己的输入引脚和输出引脚映射。在主电路中,子电路与其他门一样参与连接和计算。

组合模式类图:

text
Component (LogicElement)
├── Leaf (具体门类)
└── Composite (SubCircuit)
├── inputs: List
├── outputs: List
├── internalCircuit: Circuit
└── compute(): 触发内部电路计算,映射到外部引脚
关键实现:

子电路输入引脚映射到内部电路的INPUT信号,输出引脚映射到内部电路的OUT信号。

子电路的输出值需要递归传播,因此计算顺序既要处理主电路,也要处理子电路内部。

引用子电路时,通过子电路编号和引脚名(如“C1-A”)定位。

  1. 异常处理设计
    我创建了一个Validator类,负责在解析连接信息时按顺序检查:

连接信息中是否包含多个输出(第一个元素后还有非输入引脚)→ 多个输入异常。

连接信息中是否没有输入(除第一个元素外其余全为输出或不足)→ 无输入异常。

连接信息中是否没有输出(只有一个元素或第一个元素是输入引脚)→ 无输出异常。

输入输出顺序是否颠倒(第一个元素应为输出引脚,后续应为输入引脚)→ 顺序错误。

同一输入引脚是否被多个输出引脚连接 → 冲突异常。

检测到异常后,直接输出错误信息并终止程序,不再进行后续计算。

  1. 复杂度分析
    程序4代码行数约520,类数增至15,平均方法复杂度5.2。异常检测逻辑较为繁琐,但通过将检测拆分到独立方法,维持了可读性。

三、采坑心得

  1. 引脚编号与类型混淆
    程序2中,译码器的控制引脚、输入引脚、输出引脚编号有固定范围,但题目只给出了示例,并未在输入中明确标注。我需要根据元件类型自行分配引脚编号范围。例如,译码器M(3)1有3个输入引脚(编号35)、3个控制引脚(02)、8个输出引脚(6~13)。在解析连接信息时,必须正确判断每个引脚属于哪一类。我曾将控制引脚误当作输入,导致译码器不工作。后来我在元件构造函数中根据引脚数量动态生成引脚类型映射表,解决了此问题。

  2. 拓扑排序的依赖性遗漏
    数据选择器的输出依赖于控制引脚,而控制引脚可能来自其他门的输出。在建立依赖图时,我最初只建立了“输出引脚→输入引脚”的边,却忽略了元件输出引脚到自身内部逻辑的隐含依赖(元件输出依赖于所有输入引脚)。这导致部分情况下计算顺序错误,输出为0或未知。修正方法:在Kahn算法中,将每个元件的输出引脚作为节点,连接所有输入引脚指向输出引脚,确保输入信号先就绪。

  3. 子电路引脚映射的难点
    程序4中子电路内部使用“C”作为输出引脚名,而在主电路中引用时,需要将“C1-A”映射到子电路的输入引脚A,并将子电路的输出引脚“C”映射到外部连接。最初我直接在子电路对象中保存一个HashMap<String, Pin>,将引脚名(如“A”)映射到内部引脚,外部通过“子电路编号-引脚名”定位。但在输出时,子电路的每个内部元件输出都要加上子电路编号前缀,这要求我递归遍历子电路内的所有元件,并修改其名称显示。这一过程颇为繁琐,我最终在toString方法中动态添加前缀。

  4. 异常优先级的实现
    题目规定了5种异常的优先级顺序,我按顺序依次检查,一旦发现立即抛出异常并退出。但在同一条连接信息中可能同时存在多种异常,优先级高的先输出。我使用了if-else链,确保只输出第一个匹配的异常,符合要求。

  5. 输入解析的边界条件
    例如“INPUT: A-1 B-0”后可能有空格,连接信息“[A A(2)1-1]”内可能存在多余空格。我使用正则表达式\s+分割,并过滤空串。同时,对于子电路定义“C1:”,需要解析出编号1。最初我使用split("😊,但忽略了可能的分号,后来改用substring配合正则C(\d+):提取。

四、改进建议

  1. 使用工厂模式统一元件创建
    目前每个门类都是手动if-else判断创建,扩展新元件时需要修改多处。建议实现一个ElementFactory,根据标识符和参数反射创建对象,或在配置文件中注册,提高扩展性。

  2. 信号传播采用事件驱动模型
    当前采用拓扑排序一次性计算,对于无反馈电路足够。但程序3(时序电路)将引入反馈,需要支持多次迭代直到稳定。为提前准备,可以将计算模型改为事件驱动:每当一个引脚信号变化,触发其连接的后续元件重新计算,直到无变化为止。这为后续迭代提供更灵活的架构。

  3. 异常处理采用统一日志输出
    目前异常直接System.exit(0)终止程序,不利于调试。可以改为收集所有异常,在最后统一输出,但题目要求只输出优先级最高的,所以当前方式合理。但建议将错误信息记录到日志文件,方便开发阶段排查。

  4. 增加单元测试
    对于每个逻辑门的真值表、译码器的控制逻辑、数据选择器的选择功能,可以编写JUnit测试用例,确保每次重构后功能正确。当前仅依赖PTA测试点,覆盖不全。

  5. 优化子电路的组合模式实现
    当前子电路内部仍使用独立的Circuit对象,与主电路分离。更好的做法是将子电路作为主电路的一个节点,内部引脚与外部引脚通过代理模式连接,简化递归计算。

五、总结
通过这三次数字电路模拟程序的迭代开发,我获得了以下宝贵经验:

深入理解单一职责与开闭原则:每个元件类只负责自己的逻辑,新增元件只需添加新类,无需修改现有代码,为程序2和4的扩展提供了便利。

组合模式的实际应用:程序4中子电路的实现让我体会到组合模式如何将递归结构统一为树形处理,极大简化了复杂电路的仿真。

拓扑排序与有向无环图:通过构建依赖图并排序,我掌握了处理数据流问题的基本方法,这对后续学习编译原理、数据流分析都有帮助。

异常检测的严密性:异常优先级的实现要求我仔细分析题目规定,按顺序检查,培养了我对需求规格的精准把握能力。

输入解析的韧性:面对复杂的自定义格式,我学会了使用正则表达式和状态机,提高了处理文本数据的能力。

不足之处:

代码注释仍不够充分,尤其是复杂元件的计算逻辑和异常检测分支,缺少解释。

未考虑性能优化,当元件数量达到上千时,拓扑排序的O(V+E)尚可,但引脚映射查找可以哈希化。

对时序电路(程序3)尚未涉及,需要后续继续学习时序逻辑和状态机模拟。

后续学习方向:深入学习设计模式(如观察者模式、状态模式),并尝试将程序3的时序电路实现,掌握反馈电路的仿真技术。

这三次作业不仅提升了我的Java编程能力,更让我对数字电路和软件架构有了初步的认识,为未来从事系统仿真或硬件设计打下坚实基础。

posted @ 2026-06-24 15:31  想不起吗  阅读(1)  评论(0)    收藏  举报