面向对象设计与构造-第二次blog
数字逻辑电路仿真系统迭代开发总结
本单元以数字逻辑电路仿真为核心,进行了三次作业练习,从最基础的组合逻辑门计算逐步扩展至层次化子电路仿真。三次作业层层递进,难度逐步递增,我从中学到了很多:
• 熟练掌握Java基本语法、字符串解析、递归算法、集合框架(List/Set/Map)等基础知识点;
• 循序渐进理解系统架构设计,从过程式编码逐步向模块化、分层设计过渡;
• 深刻认识代码可拓展性、职责分离、数据结构选型的重要性;
• 学会算法迭代设计、边界情况处理,提升代码健壮性与规范性。
下面我将从程序结构、代码实现等几个方面总结本单元三次迭代作业。
第一次作业:基础门电路仿真
作业要求
设计并实现一个基础的数字逻辑电路仿真模块。系统需要支持:
• 定义输入引脚及其逻辑值(0/1);
• 声明门电路(包括与门 A(n)、或门 O(n)、非门 N、异或门 X、同或门 Y、选择器 S、多路选择器 Z(k)、全加器 F(k));
• 通过连接语法 [门名 引脚1 引脚2 ...] 建立门之间的信号传递关系;
• 递归计算每个门电路的输出值,并最终按门编号升序打印所有门输出。
实现方式
本次作业采用完全过程式编码风格,所有逻辑集中在 Main 类的静态方法中:
• 通过 parseInput 和 parseLink 解析命令行输入;
• getVal 递归查询引脚值(输入或门输出);
• calc 根据门类型计算输出,支持多输入与/或门、选择器、多路选择器(含选择信号和数据信号分离);
• print 按门类型分组(A、O、N、X、Y、S、Z、F),组内按编号升序排序,并输出结果。
代码规模
本次作业代码量较小,总计约 200 行,仅包含一个 Main 类,无任何自定义实体类或工具类拆分,结构简单,方法数量少。
类图
Main
+static String[] inputPins
+static int[] inputVals
+static int inputLen
+static String[] fromPin
+static String[] toPin
+static int linkLen
+static String[] gates
+static int gateLen
+main(String[]) : void
+parseInput(String) : void
+parseLink(String) : void
+getVal(String) : int
+calc(String) : int
+getInputCount(String) : int
+sort(String[],int) : void
+getNumber(String) : int
+print(String[],int) : void
+startsWith(String,String) : boolean
+nextSpace(String,int) : int
+getGate(String) : String
+hasGate(String) : boolean
+addGate(String) : void
不足
• 完全面向过程:未定义任何类来封装门、引脚、连接等实体,所有数据与方法杂糅在 Main 中,违反单一职责原则,可读性和可维护性极差。
• 硬编码严重:门类型判断依赖字符串前缀,扩展新门类型需修改多处 if-else,不符合开闭原则。
• 排序算法简陋:使用冒泡排序且只按门编号升序,未考虑相同编号冲突。
• 输入校验缺失:对输入格式、数值范围、引脚存在性等均无校验,异常输入易导致数组越界或死循环。
• 全加器输出逻辑异常:print 中 F(k) 的打印逻辑仅输出若干 -,并未真正计算和输出多个输出位(未完成)。
第二次作业:门电路增强仿真(多类型输出与分组优化)
作业要求
在第一次作业基础上,要求系统能够正确处理更多类型的门电路,并优化输出格式:
• 除基础门(与、或、非、异或、同或)外,新增选择器(S)(输出由控制端使能)、多路选择器(Z(k))(选择信号控制数据输出)、全加器(F(k))(输出选择索引对应的位,其余位输出 -);
• 门电路按类型分组输出,同一组内按编号升序排列;
• 对于选择器,若控制端为0则不输出(输出 null);对于多路选择器和全加器,仅输出有效数据引脚的结果,全加器需输出固定长度的位串。
实现方式
本次作业在过程式框架上进行了针对性优化:
• 静态数组分组存储:将门按类型分别存入 A, O, N, X, Y, S, Z, F 数组,并各自维护长度计数器,避免第一次作业中反复遍历 gates 数组和动态分组。
• 功能分离:将不同门类型的计算逻辑拆分为独立方法(calcBase 处理常规门,calcS、calcZ、calcF分别处理特殊门),并将打印逻辑也按类型拆分为 printAOXYN、printS、printZ、printF,减少了 calc 和 print 中的庞大分支。
• 全加器输出优化:calcF 正确计算选择索引,并生成对应长度的字符串,仅在选择位输出数据,其余位填充 -,符合要求。
• 输入数组容量扩大:从100增至200,提高可处理规模。
代码规模
仍为单文件 Main 类,总行数约 260 行,方法数量增加至16个,但类结构未变。
类图
Main
+static String[] inputPins / inputVals
+static String[] fromPin / toPin
+static String[] gates / A/O/N/X/Y/S/Z/F
+static int 各长度计数器
+startsWith / nextSpace / getGate / hasGate / addGate
+main(String[]) : void
+parseInput(String) : void
+parseLink(String) : void
+getVal(String) : int
+calcBase(String) : int
+getInputCount(String) : int
+calcS(String) : Integer
+calcZ(String) : Integer
+calcF(String) : String
+sort(String[],int) : void
+getNumber(String) : int
+printAOXYN(String[],int) : void
+printS(int) : void
+printZ(int) : void
+printF(int) : void
相较第一次的改进
- 分组存储与独立处理:在解析阶段就将门分类,后续无需重复类型判断,提高了执行效率。
- 方法职责细化:将不同类型门计算拆分为独立方法,主流程更加清晰,也便于单独测试每种门逻辑。
- 特殊门电路逻辑完善:选择器、多路选择器、全加器的计算和输出均按规范实现,尤其全加器正确生成位串,弥补了第一次作业中的未完成部分。
- 输出规范化:每个门输出均标注引脚号(或对于全加器直接输出位串),格式符合预期。
仍存在的不足 - 依旧完全面向过程:所有数据和方法仍在单一 Main 类中,未定义 Gate、Pin、Connection 等实体类,扩展新门类型依然需修改多处静态方法,不符合开闭原则。
- 数据冗余与混乱:同时维护 gates 全量数组和多个分组数组,容易造成数据不一致。
- 硬编码依然严重:门类型判断仍依赖字符串前缀,且 getInputCount 中对参数个数硬编码,无法支持可变参数。
第三次作业:层次化门电路仿真系统(子电路与模块化)
作业要求
在前两次作业的基础上,要求系统支持层次化设计:
• 用户可以定义子电路(如 C1:、C2: 等),内部包含独立的输入引脚、输出引脚、门电路和连接关系;
• 子电路可作为黑盒被上层引用(例如引脚 C1-x 表示子电路 C1 的内部引脚 x);
• 系统需递归计算每个子电路内部所有门和连线的逻辑值,最终输出所有顶层和子电路内部的门输出(带子电路前缀);
• 同时保留原有的顶层门电路仿真能力,整体功能高度集成。
实现方式
本次作业在过程式框架上进行了大规模重构,但依然未引入面向对象实体类:
• 子电路建模:定义静态内部类 SubCircuit,包含 id、输入输出引脚列表、内部连线表(fPins/tPins)以及门集合(gates)。所有子电路实例存储在 Map<String, SubCircuit> 中。
• 集合框架全面替代数组:使用 HashSet 存储所有门(allGates),用 ArrayList 动态分组,避免了固定容量限制和越界风险。
• 层次化值传播算法:simulateSubCircuit 方法实现子电路内部的不动点迭代(do-while changed),反复计算门输出和连线传播,直至网络状态稳定。该算法虽简单但可处理组合逻辑环(若有)会死循环,但至少提供了层次化模拟框架。
• 递归取值增强:getVal 方法支持识别 C1-xxx 格式的引脚,调用对应子电路的模拟结果查询内部引脚值。
• 输出全面整合:printGates 先输出顶层门,再遍历所有子电路,输出其中包含的对应类型门的结果(带子电路前缀)。
代码规模
单文件,总行数约 300 行,包含一个内部静态类,方法数量增至约18个,整体复杂度显著提升。
类图
Main
+static String[] inputPins / inputVals
+static String[] fromPin / toPin
+static int inputLen / linkLen
+static SetallGates
+static Map<String, SubCircuit> subMap
+main(String[]) : void
+parseAll(Scanner) : void
+getVal(String) : int
+simulateSubCircuit(String) : Map<String,Integer>
+getInputCount(String) : int
+computeGateValue(String,int[]) : int
+getGate(String) : String
+sort(List) : void
+getNumber(String) : int
+printGates(List,String) : void
+isInSubCircuit(String) : boolean
+calc(String) : int
SubCircuit
+String id
+Listinputs
+Listoutputs
+ListfPins
+ListtPins
+Setgates
相较第二次的重大改进 - 层次化设计能力:支持子电路封装,极大增强了系统的表达能力和复用性,可模拟更复杂的数字系统。
- 数据结构现代化:彻底抛弃固定数组,使用 ArrayList、HashSet、HashMap,消除了容量限制,并简化了去重和查找。
- 子电路内部仿真算法:采用迭代传播(changed 循环)模拟组合逻辑,为层次化计算提供了可行方案,扩展了系统可处理电路的规模。
- 输出信息增强:能区分顶层门和子电路内门,并正确添加前缀,便于调试和验证。
- 输入解析全面重构:统一的 parseAll 处理顶层输入、连线以及子电路定义,使解析逻辑更加集中。
仍存在的不足 - 完全面向过程:虽然引入了内部类 SubCircuit,但本质上仍是数据容器,所有操作逻辑(解析、模拟、输出)依然堆积在 Main 的静态方法中,未将行为分配到对象上(如 SubCircuit.simulate() 应为实例方法)。违反单一职责和开闭原则。
- 算法缺陷:simulateSubCircuit 中的不动点迭代对无环组合电路有效,但若存在逻辑环(如非门反馈)则会陷入无限循环,未做检测或限制迭代次数。
- 性能问题:每次 getVal 若遇到子电路引脚,都会重新执行 simulateSubCircuit 完整模拟,无缓存机制,对于多次查询重复计算,效率低下。
- 硬编码类型判断:getInputCount 和 computeGateValue 依然依赖首字母 if-else 分支,每增加新门类型仍需修改多处,未使用多态或策略模式。
- 混合命名与逻辑混乱:fromPin/toPin 为全局数组,同时子电路内部也维护了自己的 fPins/tPins,二者并存且互相引用,导致理解困难,容易产生数据不一致。
总结
经过本单元三次数字逻辑电路仿真系列作业的迭代练习,我从基础语法入门,到功能模块拆分,再到支持层次化子电路仿真,全过程收获十分充实。
从代码编写层面来看,三次作业难度循序渐进:第一次作业仅完成基本门电路计算与简单输出;第二次作业引入分组存储、特殊门独立方法,开始建立功能分离意识;第三次作业借助集合框架、子电路建模、不动点迭代算法,融入层次化设计,让代码结构在复杂度大幅提升的同时,仍保持了相对清晰的流程。我也从最初将所有逻辑堆砌在主方法中,慢慢学会拆分功能方法、使用合适的数据结构,有效降低了方法圈复杂度,提升了代码可读性与可维护性。
在面向对象思想运用上,本单元我虽然掌握了类定义、内部类使用、集合框架等知识点,但遗憾地未能将真正的面向对象设计落地——三次作业始终停留在过程式静态方法层面,未定义独立的门电路类、引脚类、连接类,也未使用继承、多态或设计模式。这导致代码扩展性极差,每次新增门类型都需修改多处分支,违背开闭原则。自身明显不足在于对面向对象建模、多态应用以及设计模式的运用十分生疏,代码复用性和可测试性仍有巨大提升空间。
浙公网安备 33010602011771号