第二次blog作业

一、前言:三次作业集总体分析
1.1 作业概览
第四次作业(基础逻辑门仿真系统) 实现了与门、或门、非门、异或门、同或门五种基本逻辑门,支持多输入与门和或门。系统采用扁平化架构,使用队列实现信号传播,完成了一个简单但完整的数字电路仿真器。回过头来看,这是三次作业的基石,重点训练了面向对象的基本概念、集合框架的使用和基础算法的实现。

第五次作业(增强型逻辑电路仿真系统) 在第四次作业基础上大幅扩展了组件类型,新增了三态门、译码器、多路选择器、多路分配器等复杂数字逻辑组件。组件类型从5种扩展到9种,代码量显著增加。这次作业的重点是理解复杂数字逻辑组件的工作原理,并将其正确实现。同时引入了组件的参数化命名格式。

第六次作业(层次化电路仿真系统) 在前两次作业的基础上进行了架构层面的重构,引入了子电路概念,支持通过C...endc块定义可复用的电路模块。这是三次作业中难度最大的一次,需要设计支持层次化仿真的架构,处理跨层次的信号传播,实现模块的封装和复用。

1.2 难度递进分析
从难度上看,三次作业呈现出明显的递进关系。第四次作业的核心挑战是理解面向对象编程思维和基本的数据结构应用,对于我这样的初学者来说,理解为什么要用抽象类、接口、继承和多态,需要很大的思维转变。

第五次作业的核心挑战是理解复杂数字逻辑组件的工作原理。译码器、多路选择器等组件涉及二进制编码、选择逻辑等概念,需要有一定的数字电路基础知识。同时,命名格式的复杂化也增加了输入解析的难度。

第六次作业的核心挑战是层次化架构的设计。如何让子电路既能封装内部实现,又能像基本逻辑门一样被使用,需要巧妙的类设计。跨层次的信号传播、迭代收敛算法的实现都是需要深入思考的技术难点。

1.3 整体评价
三次作业形成了一个完整的数字电路仿真知识体系。每次作业都在前一次的基础上增加新的挑战,避免了断崖式的难度跳跃。而且每个作业都是一个完整的系统,有明确的输入输出,运行起来能看到实际效果,每次写完都感到很有成就感

二、设计与分析
2.1 第四次作业架构设计
第四次作业采用了较为直接的设计方式。系统的核心是Gate类,包含门类型、输入数量、输入引脚列表、输出计算状态和输出值。Main类负责所有的输入解析、信号传播和结果输出。

这种设计的优点是实现简单、代码集中,适合快速开发。缺点也很明显:所有逻辑都集中在Main类中,违反了单一职责原则。信号传播部分使用了队列实现拓扑排序传播,算法效率较高。

屏幕截图 2026-06-24 182334
屏幕截图 2026-06-24 190116

2.2 第五次作业:增强型逻辑电路仿真
第五次作业在架构上沿用了第四次作业的扁平化设计,但在组件建模上进行了重要改进。这次引入了抽象基类Component,所有具体逻辑门都继承自这个基类。

Component抽象类定义了组件的核心行为:输出值数组outputValues、输入连接列表inputConnections、添加输入连接的方法addInputConnection、抽象的compute计算方法、以及获取所有输入值的方法getAllInputValues。这种设计将组件的共同行为抽取到了基类中,每个具体组件只需要实现自己的compute方法即可。

具体组件类如AndGate、OrGate、TriStateGate、Decoder、Mux、Demux等都继承自Component并实现了compute方法。每个组件的compute方法都遵循类似的模式:检查输入数量是否正确、获取所有输入值、根据逻辑规则计算输出、存储结果。

Decoder(译码器)的实现比较复杂,需要处理使能信号和编码逻辑。译码器有3个控制引脚S1、S2、S3,当S1为高电平且S2和S3为低电平时,译码器被使能。然后根据输入编码将对应的输出引脚置为低电平,其他输出引脚为高电平。这种低电平有效的输出方式是数字电路中常见的设计。

Mux(多路选择器)根据控制信号从多个数据输入中选择一个输出。控制信号的数量决定了数据输入的数量,如果有n位控制信号,则有2^n个数据输入。Mux的compute方法需要解析控制信号的值,计算出选择的数据输入索引,然后输出对应的数据值。

Demux(多路分配器)与Mux相反,它将一个数据输入根据控制信号分配到多个输出中的一个。Demux的compute方法将数据值复制到选中的输出引脚,其他输出引脚保持无效状态。
屏幕截图 2026-06-24 182718
屏幕截图 2026-06-24 185919

2.3 第六次作业:层次化电路仿真
第六次作业在架构上进行了重大改进,引入了子电路概念。这次设计的核心变化是:电路不再是扁平的组件集合,而是可以包含层次化结构,子电路可以像基本组件一样被使用。

CircuitComponent是整个系统的基类,它定义了组件的基本行为。所有逻辑门和子电路都继承自这个类。每个组件都有名称、输入映射、输出值等属性,以及设置输入、计算输出、判断就绪等方法。

SubCircuit类是第六次作业的核心创新。它继承自CircuitComponent,因此可以像基本组件一样被使用。但SubCircuit内部包含了完整的电路结构:输入端口列表、输出端口列表、内部组件映射、内部连接列表。这种递归结构使得电路可以无限嵌套。

SubCircuit的propagateSignals方法实现了子电路内部的信号传播。它需要处理从外部输入到内部组件的信号传递、内部组件之间的信号传播、以及从内部组件到外部输出的信号汇聚。

屏幕截图 2026-06-24 182925

屏幕截图 2026-06-24 185146

三、采坑心得
3.1 组件命名解析的复杂性
组件命名解析是三次作业中最容易出问题的地方。
第四次作业中,命名格式相对简单,如"A1"表示与门1,"O2"表示或门2。解析时只需要根据首字母判断类型,从剩余部分提取编号即可。

第五次作业中,命名格式变得复杂,引入了参数化命名。如"A(3)1"表示3输入与门1,"M2"表示2输入译码器2。解析时不仅需要判断类型和编号,还需要提取括号中的参数。我在实现parseComponentName方法时,最初没有考虑参数部分可能包含多位数的情况,导致解析"M10"时出错。后来通过先定位右括号的位置,再提取编号的方式解决了问题。

第六次作业中,命名格式进一步复杂化,增加了子电路的命名。子电路的ID可能包含字母和数字,如"C1"、"CSub2"等。解析时需要判断一个名称是基本组件还是子电路实例,这需要通过查询MainCircuit的subCircuits映射来确定。

3.2 信号传播算法的选择
第四次作业使用了拓扑排序算法,这种方法效率高,但要求电路是组合逻辑(无反馈)。第二次作业改用迭代收敛算法,虽然效率略低,但适用范围更广。

在第五次作业中,我最初照搬了第一次作业的拓扑排序算法,结果发现无法处理含有译码器等组件的电路。原因是译码器的输出依赖于多个输入信号,这些信号可能来自不同的路径,拓扑排序难以处理这种多输入依赖的情况。后来改用迭代收敛算法,问题得到解决。

第六次作业中,由于引入了子电路,信号传播变得更加复杂。子电路内部的传播和顶层电路的传播需要协调进行。我最初的处理方式是将子电路完全展开后再传播信号,但这种方式破坏了子电路的封装性。后来改为让子电路独立传播,只通过输入输出端口与外界交互,这种方式既保持了封装性,又简化了传播逻辑。

3.3 迭代收敛的最大迭代次数
迭代收敛算法需要设置最大迭代次数来防止无限循环。我最初设置的是100次,但在测试一些复杂电路时发现100次不够,有些电路需要更多次迭代才能收敛。

经过分析,迭代次数与电路中最长路径的级数有关。如果一个信号需要经过10个逻辑门才能到达输出,至少需要10次迭代。如果电路中有复杂的反馈路径,需要的迭代次数更多。

我后来将最大迭代次数调整为1000次,并增加了提前终止的优化:如果某次迭代没有任何组件的输出发生变化,就提前结束迭代。这种优化大大减少了不必要的迭代。

3.4 浮点数与整数的精度问题
虽然数字电路只有0和1两个状态,但解析输入值时可能遇到浮点数。比如用户可能输入"1.0"而不是"1"。我最初使用Integer.parseInt直接解析,遇到"1.0"时会抛出NumberFormatException。

为了解决这个问题,我增加了输入值的预处理:先尝试解析为整数,如果失败则尝试解析为浮点数,然后四舍五入取整。如果用户输入"0.5"这样的值,按照数字电路的规则应该报错,但按照四舍五入取整会得到"1",这可能隐藏用户的输入错误。

更合理的方式是严格验证输入值必须是整数0或1,如果不是则报错。但考虑到用户体验,我在三次作业中采用了不同的策略:第一次作业严格验证,第二次作业增加了容错处理,第三次作业则通过错误列表收集所有错误后统一输出。

总体来说我觉得第五次作业是最难的,因为它是我这三次代码中写的最低分也是所有六次代码中的最低分,是始终不明白为什么他的输出为什么是空

四、改进建议
4.1 架构层面的改进
当前的架构虽然支持了层次化设计,但还有一些可以改进的地方。首先,可以引入访问者模式来实现电路的分析和遍历。访问者模式可以将操作(如信号传播、输出格式化)与数据结构(组件和连接)分离,使得添加新的操作更加容易。

其次,可以引入观察者模式来实现信号的自动传播。当某个组件的输出发生变化时,自动通知所有依赖该输出的组件更新状态。这种模式更适合时序逻辑电路的仿真。

最后,可以引入工厂模式来创建组件,根据组件名称自动创建对应的组件实例。这可以将组件创建逻辑从Main类中分离出来,提高代码的可维护性。

4.2 功能层面的改进
当前系统只支持组合逻辑电路,不支持触发器、寄存器等时序逻辑元件。增加时序逻辑支持是最大的功能扩展方向。时序逻辑需要引入时钟信号和状态存储,这需要修改信号传播算法,从单纯的信号传播变为状态更新。

增加波形输出功能可以大大提升系统的调试能力。当前只能看到电路最终的输出状态,无法看到信号随时间的变化。波形输出可以记录每个时间点各组件的输出值,以波形图的形式展示。

支持Tcl或其他脚本语言的交互式命令行可以提升用户体验。用户可以在命令行中输入命令来设置输入信号、查看输出状态、添加新的组件等,而不需要每次修改输入文件后重新运行。

4.3 代码质量层面的改进
当前的代码缺少单元测试,这是一个很大的不足。建议使用JUnit建立完整的测试套件,覆盖各种逻辑门类型、连接关系、子电路场景。每个测试用例应该包括输入和期望的输出,在代码修改后自动运行测试,确保没有破坏原有功能。

代码中还有一些重复代码可以抽取成公共方法。比如组件名称的解析逻辑在多个地方出现,可以抽取到单独的Util类中。排序输出组件的逻辑也重复出现,可以抽取到OutputFormatter类中。

异常处理方面,当前代码在遇到错误时有些地方直接返回,有些地方抛出异常,有些地方记录到错误列表。建议统一异常处理策略,所有错误都记录到错误列表,最后统一输出。

五、总结
5.1 学习收获
通过三次作业的实践,我深入理解了面向对象设计的基本原则。第四次作业中,所有代码集中在Main类中,职责分配不均衡。第五次作业引入了抽象基类Component,将共同行为抽取到了基类中。第六次作业引入了子电路概念,实现了模块化设计。三次迭代让我体会到了单一职责原则、开闭原则、依赖倒置原则在实际开发中的应用。

信号传播算法的演进也让我受益匪浅。从拓扑排序到迭代收敛,再到多层次协调传播,每次算法的改进都伴随着对问题理解的深入。我学会了根据问题的特点选择合适的算法,而不是一味地使用同一种方法。

数据结构的选择和使用也是重要的收获。Map用于存储组件和连接关系,List用于存储有序的引脚列表,Set用于去重和快速查找。这些基础数据结构的灵活运用是编写高效Java程序的关键。

5.2 待深入研究的方向
设计模式是值得进一步深入研究的方向。访问者模式、观察者模式、工厂模式在电路仿真系统中都有应用场景,学习这些设计模式可以帮助写出更加优雅和可维护的代码。

并发编程是另一个值得研究的方向。当仿真大规模电路时,可以利用多线程并行计算不同组件的输出,提升仿真效率。但需要处理好信号的同步问题,避免出现竞争条件。

数字电路理论本身也值得深入学习。理解时序逻辑、状态机、流水线等概念,可以帮助设计更加复杂和高效的电路结构。

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