关于“数字电路模拟”三次作业的三版代码迭代实现
一、引言
本次OOP4-6的作业要求在已有数字电路模拟程序(基础门级)的基础上,新增子电路定义与引用功能,并完善异常输入检测体系。这二进制数制系统相对应,使得数字电路成为所有计算机和数字系统的物理实现基础。程序需解析我们输入的文本描述,构建电路网络,计算各逻辑门输出,并按指定格式输出结果。
二、涉及到的知识点解析
1、数字电路基础
题目要求模拟五种基本逻辑门(与、或、非、异或、同或)及子电路。理解门的真值表(如与门“全1出1”、或门“全0出0”)是正确实现计算逻辑的前提。非门单输入反相,异或/同或基于奇偶校验。此外,子电路概念引入了层次化设计思想,将多个门封装为一个“黑盒”,对外仅暴露输入/输出引脚,内部逻辑对外部透明。这要求程序支持递归嵌套(子电路内部可再引用其他子电路),对数据结构设计提出挑战。
2、对输入规则进行总结
元件定义:如 A(8)1 表示8输入与门编号1,X5 表示异或门编号5。
子电路模板:以 C编号: 开头,包含 INPUT:、OUT:、连接信息列表、endc 结束标志。
主电路:包含 INPUT: 信号赋值、连接信息列表([输出引脚 输入引脚1 输入引脚2 ...])、end 结束。
引脚表示:如 A(2)1-0(与门输出)、C1-A(子电路1的A引脚)。
3、对异常情况处理的升级和优先级规则
①连接信息中包含多个输入(目标引脚≥2)
②连接信息中没有输入(目标引脚为空且源引脚为输入引脚)
③连接信息中没有输出(目标引脚为空且源引脚为输出引脚)
④输入输出顺序写反(源不是输出或目标是输出)
⑤输入引脚冲突(同一输入引脚被多个输出驱动)
三、具体实现
(一)作业四:基础逻辑门元件构成电路
1、类的封装:Component 类(包含元件的name,type,id,inputCount输入引脚数量等信息),Input 类,Pin封装引脚链接为对象,Input输入封闭只作为数据容器
2、职责分配:findComponent(String name) —— 查找元件;parseComponent(String compName) —— 解析并创建元件(根据元件名称字符串,解析其类型、输入引脚数、编号,并实例化 Component 对象。对于新创建的 Component 对象,若格式非法则返回 null。);expandComponents() —— 扩容元件数组(当 components 数组容量不足时,将其长度扩展为原来的 2 倍,以容纳更多元件。,返回值void);getSignal(String source) —— 获取信号值(含参数Source识别信号来源字符串,例如 A(外部输入)或 A(2)1-0(门输出),逻辑是若源为外部输入,直接返回其值;若源为门输出引脚,则调用 comOutput 计算对应门输出并返回;若无法定位,返回 null。);comOutput(Component comp) —— 计算门输出(计算后的输出值(Integer),若输入依赖不完整则返回 null。);compareComponent(Component a, Component b) —— 元件比较器(参数为两个待比较的 Component 对象。)
3、一些问题:Component 和 Input 只存数据,所有操作(如添加输入源、计算输出)都在外部完成,不符合面向对象的封装思想。对象应拥有自己的行为。除此之外我是使用静态数组和计数器作为全局数据库,增加了代码耦合和出错风险(如数组越界、空指针)。其次缺乏错误处理:对非法输入(如缺失源、引脚号越界)没有检测,程序可能运行失败或抛出异常。
4、心得:我可以将部分程序重构,如创建Gate 抽象类定义 compute() 方法,由各类门(AndGate、OrGate 等)继承实现,利用多态,方便后续扩展(如增加子电路、异常检测)和单元测试。。
(二)作业五:扩展内容要求:包含多输入输出的组合电路元件如数据选择器以及元件引脚类型除输入、输出之外,增加控制引脚,如三态门。
1、类与对象:
Component 类(包含核心元件):存储元件的静态属性(kind, number, width)和动态状态(inputs/outputs 引脚列表、sources 输入源、valid 标志)。包含 buildPins() 和 setSource() 方法,体现了一定程度的行为内聚;ComponentInfo 类作为解析 parseComponentName 的返回值,存储类型、ID、参数(如输入数)。
Pair<A, B> 类(二元组类):在解析连接信息时创建,存入列表(List<Pair<String, List
2、职责分配:静态方法parseComponentName:元件名称字符串,提取类型、编号、参数;getOrCreateComponent:获取或创建元件对象;computeComponent:根据元件类型和输入值计算输出信号,返回键值对
3、新增功能:引脚模型:支持多输入/多输出、可选引脚(三态门高阻态);数据结构:使用 Map 替代数组,更安全灵活;元件异常验证:增加 valid 标志,过滤非法引脚;算法:从递归深度优先改为拓扑排序以便于增强程序的鲁棒性。
4、反思心得:OOP5作业五的代码在元件种类和计算策略上优于OOP4,在新增了4种组合电路元件的基础上,将计算从递归改为拓扑排序,提高了处理复杂电路的能力。但在面向对象设计和题目要求的异常/子电路功能方面,两版均有明显不足。OOP5虽然代码更长、架构更复杂,但并未真正采用组合模式或接口多态,本质上仍是“数据+过程”的风格,扩展性有限。
(三)作业六:扩展内容要求:增加子电路;增加程序异常输入的检测。
1、类与对象:Component:抽象基类,定义 id 和 calculated 标志,无抽象方法(仅作为类型标识)。使用了向上转型和向下转型(如在 resolvePin 方法中,从 componentMap 取出的对象声明为 Component,通过 instanceof 判断具体类型并向下转型)
Gate:表示基本逻辑门,包含类型 type、编号 number、输入引脚列表 inputPins、输出引脚 outputPin,实现 calculate(int[]) 方法计算输出。
SubCircuitInstance:表示子电路实例,包含 circuitId、template(定义模板)、internalContext(内部上下文),以及对外暴露的 inputPinMap 和 outputPinMap。
2、错误处理机制更新:使用静态变量 firstError 保存第一个异常信息,通过 setError() 设置。
在 processMainCircuit 和 SubCircuitInstance 构造函数中,对 RuntimeException 进行捕获并调用 setError,最终在 main 中输出错误。
异常检测函数 checkConnection 严格按题目优先级(1→2→3→4→5)返回错误信息,若检测到冲突则抛出异常。
3、职责分配:parseSubcircuit:解析单个子电路模板(C... 到 endc),填充 CircuitTemplate;recordGateDef:从引脚字符串中提取门定义,去重后加入模板的 gateDefs;checkConnection:按优先级检查连接信息,返回错误信息或 null;resolvePin:根据引脚字符串(门引脚、子电路引脚、外部输入)解析为 Pin 对象;Pin 构造函数:初始化引脚属性;Gate.calculate:根据输入值数组计算输出,并写入 outputPin.value;SubCircuitInstance 构造函数:根据模板创建子电路实例,建立内部连接和引脚映射;引脚解析扩展:增加 SUB_PIN_PATTERN 正则,支持 C编号-引脚名 格式,能区分子电路的输入/输出引脚。
4、模板存储:使用静态 templateMap(Map<Integer, CircuitTemplate>)按编号存储所有子电路模板。
5、子电路实例化:新增 SubCircuitInstance 类,在 resolvePin 中遇到 C1-A 格式时动态创建实例,并建立内部引脚映射。
6、反思心得:在本次代码编辑中,SubCircuitInstance 继承 Component,与 Gate 统一对待,在 collectGates 和 resolvePin 中多态处理,体现了多态统一容器,递归收集,扩展性强的优势(如collectGates 能统一处理主电路和子电路内的门,无需为每种元件类型编写单独遍历逻辑);新增使用自定义异常类,避免抛出 RuntimeException 并用字符串判断;分离职责:将解析器、构建器、模拟器、错误处理器拆分为独立类,降低 Main 的复杂度。但同时应该加强封装,将 Pin、Gate 等类的字段设为 private,提供必要的访问方法。
五、涉及到的面向对象编程原则
1、单一职责原则(SRP)
优化之后,过去由Main 承担了输入解析、元件管理、信号计算、排序、输出所有职责,类过于臃肿问题被解决,并被拆分为解析器(Parser)、电路网络(Circuit)等独立模块。
2、增加新门类型需要修改 parseComponent 的正则和 comOutput 的 switch,体现了开闭原则
3、组合/聚合的应用
如OOP6中Gate 类组合了多个 Pin 对象,通过成员变量 inputPins 和 outputPin 持有 Pin 对象,利用组合构建引脚集合;SubCircuitInstance 类组合了 CircuitContext 和 CircuitTemplate;CircuitTemplate 类聚合了定义列表,通过组合多个列表来存储子电路的定义信息,将职责委托给外部。
六、总结
在本次课程作业中,我先后编写了三个版本的数字电路模拟程序,从最初仅支持五种基本逻辑门的基础版本,到后来逐步加入组合电路元件、子电路定义以及完整的异常检测机制。回顾这一过程,我不仅在编程技能上有所提升,更重要的是对软件设计方法和工程实践有了更切身的体会,同时我认为本次作业的难度极大,自己水平有限很难完成全部实验要求。
起初,我按照面向过程的方式快速实现了第一版程序,将所有数据和操作都放在了Main类的静态方法中,利用全局数组管理元件和输入信号,通过递归计算各门的输出。当时觉得这样写直观快捷,功能也能跑通,并没有考虑后续扩展的问题。但当看到第二版需求中新增了多种组合电路元件时,我立刻意识到了这种写法的局限:每增加一种元件类型,就要修改解析、计算、引脚定义等多个地方,代码逐渐变得臃肿且难以维护。
第二版的开发促使我开始将元件抽象为Component类,把引脚列表和源连接等信息封装在一起,并引入拓扑排序替代原先的递归计算,使程序在处理复杂依赖关系时更加稳健。虽然这一版仍然将计算逻辑放在外部静态方法中,但数据结构已经变得更加清晰,也为后续进一步重构打下了基础。
到了第三版,面对子电路和异常检测两大核心功能,我意识到必须改变设计思路。通过引入组合模式,我将基本门和子电路统一为Component的子类,使得主电路和子电路在引脚解析、门收集、输出排序等环节能够被统一处理。这种“部分-整体”的结构让我感受到了设计模式的实际价值,递归遍历子电路内部的门变得非常自然,代码的扩展性也得到了明显改善。与此同时,异常检测模块按优先级依次检查五种错误情况,并在发现第一条异常后立即终止处理,既满足了题目要求,也体现了尽早暴露问题的设计理念。
这次迭代开发也让我认识到,设计并非一蹴而就,而是随着需求的变化逐步演进的。如果一开始就投入大量精力做完美的设计,可能会陷入过度工程,但若始终停留在第一版的水准,则难以应对功能的扩展。合理的节奏应当是先实现核心功能保证可用,再通过重构不断优化结构。像第三版最核心的设计是组合模式:子电路内部可以包含基本门或其他子电路,形成递归嵌套结构。collectGates方法递归遍历上下文收集所有门,resolvePin方法统一处理门引脚和子电路引脚,这种“部分-整体”的层次化处理是组合模式的精髓。
作业四OOP4:

作业五OOP5:


作业六OOP6:



浙公网安备 33010602011771号