Java OOP实训作业集4~6总结报告

一、前言
1.1 知识点覆盖全景
三次作业集构成了一条从基础语法到专业领域应用的Java面向对象编程学习路径,知识点覆盖呈现出循序渐进的特点。
作业集4(基础逻辑门仿真)主要涉及了接口的定义与实现、抽象类的继承与扩展、集合框架中HashMap和ArrayList的使用、正则表达式对输入文本的解析、以及信号传播过程中广度优先搜索思想的初步运用,这一阶段的核心目标是理解继承体系的价值所在,也就是将五种逻辑门的公共属性与行为抽取到抽象父类中,再将各门电路特有的计算逻辑留给子类去实现。
作业集5(复杂元件与架构重构)在作业集4的基础上引入了枚举类型的定义与使用、工厂模式的雏形实现、控制引脚与数据引脚的分类管理方法、多输出元件的处理策略、以及拓扑排序思想在电路计算中的初步应用,这一阶段着重体现了统一组件抽象的设计思路,也就是将门电路、译码器、选择器等不同类型的元件纳入同一个类型体系之下。
作业集6(子电路与异常处理)涉及了组合设计模式的应用、子电路定义的递归下降解析、事件驱动模型在信号传播中的实现、以及五类电路语义错误的检测与报告机制,这一阶段要求开发者理解层次化设计对于管理复杂系统的重要性。
从三次作业的技术深度来看,呈现出的是一条螺旋式上升的路径,即从单体门电路仿真开始,逐步过渡到异构元件的统一管理,最后发展到层次化子电路的嵌套设计,这样的安排符合由浅入深的认知规律,让学习者能够逐步建立起面向对象设计的基本感觉,同时也在不断面对新的架构挑战。
1.2 题量与难度分析
作业集4的代码总量大约在三百二十行左右,涉及十个类(其中包含内部类和接口),具体门电路子类有五个,难度定位在入门级别,核心挑战在于理解抽象类与接口之间的协作方式、信号在引脚之间的传递机制、以及固定点迭代算法在处理边界条件时需要注意的问题,预计完成时间在三到四个小时。
作业集5的代码总量大约在四百五十行左右,涉及十一个类(包含一个枚举类型),新增了四种复杂元件的实现逻辑,难度属于中等偏上的级别,主要挑战体现在九种异构元件的统一创建与管理、各元件引脚编号规则的差异化映射、以及两阶段传播算法正确性的保证上,预计完成时间在五到六个小时。
作业集6的代码总量大约在六百四十行左右,涉及十一个类外加一个内部接口,引入了子电路定义与实例化的完整机制以及五类异常检测功能,难度处于进阶级别,核心挑战在于子电路的递归展开与信号隔离、BFS事件驱动传播模型的正确实现、以及各异常检测规则优先级的准确把握,预计完成时间在八到十个小时。
个人实际完成三次作业的耗时情况是:作业集4用了大约三个小时,作业集5用了大约五个小时,作业集6用了大约八个小时,此外Bug修复和调试额外增加了约四个小时,主要集中在作业集5的引脚映射错误和作业集6的子电路递归实例化问题上。
1.3 整体完成质量评估
三次作业在功能实现层面都达到了基本要求,但各自都存在不同程度的架构缺陷和逻辑漏洞。
作业集4的五种门电路逻辑计算都是正确的,信号传播在大多数测试用例下也能够正常收敛,但固定点迭代算法依赖于人工设定的最大迭代次数上限,当电路级联深度较大时可能出现尚未收敛的情况,这个问题本质上是因为信号传播算法的选择不够严谨。
作业集5的九种元件基本计算功能都是正确的,但架构方面存在比较明显的问题,Main类承担了过多的职责,其中createComponent方法的圈复杂度达到了二十四,属于典型的上帝方法,同时数据分配器的多输出特性也未能融入统一的信号传播框架,这是一个设计层面的缺陷。
作业集6的核心计算功能是正常的,BFS事件驱动传播模型的运行效果良好,子电路的层次化展开也基本正确,但在检测到异常时部分解析工作已经完成,系统中残留了部分已创建的对象,虽然不影响最终的错误输出,但这反映了先执行后验证的不安全编程习惯。
三次作业的整体质量呈现出功能可用但架构不够优雅的特点,反映出对设计原则的实践经验和异常情况考虑的不足。
二、设计与分析
2.1 作业集4:基础逻辑门电路仿真
作业集4采用了接口加抽象父类加具体子类的三层继承结构,Pin接口定义了引脚的行为规范,包括获取名称、获取值、设置值、添加信号源、获取信号源列表以及判断是否为外部输入等操作,BasePin类实现了这个接口并持有名称、整数值、信号源列表和外部标志等属性,Gate接口则定义了门电路的行为,包括获取名称、获取输出引脚、执行计算、判断有效性和获取类型及编号。
LogicGate是抽象类,它同时实现了Gate接口并作为所有具体门电路的父类,这个类封装了各门电路的公共属性,也就是名称、输入引脚映射表、输出引脚对象、有效性标志、编号、类型和输入数量,它还提供了获取输入引脚、获取输入值以及判断所有输入是否有效等辅助方法,这些方法可以被子类直接复用,体现了继承在代码复用方面的价值,五个具体门电路类分别重写了compute方法来实现各自对应的逻辑运算。
Circuit类负责管理整个电路,它持有门电路名称到对象的映射表、门列表、所有引脚的映射表以及外部输入的映射表,这个类需要解析引脚名称并创建对应的门和引脚对象、建立输出引脚到输入引脚之间的连接关系、执行信号传播计算以及按规范格式输出结果,Main类作为程序入口负责逐行读取输入内容并识别INPUT行和连接行,然后调用Circuit类的方法完成仿真并输出结果。
从代码度量数据来看,作业集4的平均圈复杂度约为二点八七,整体处于可接受的范围之内,但最大圈复杂度达到了十八,出现在Circuit类的getOrCreatePin方法中,这个方法承担了根据引脚名解析门名、创建门对象、注册所有引脚、返回目标引脚等多重职责,内部包含了多个条件分支和正则匹配以及Map操作,独立路径数比较多,明显违反了单一职责原则,最大嵌套深度为七层,出现在Main类的输入解析循环中,这在一定程度上影响了代码的可读性,分支语句的占比大约是百分之二十八。
信号传播采用的是固定点迭代算法,每一轮都会遍历所有门电路并依次调用compute方法,compute方法内部通过getInputValue方法从引脚的sources列表中获取信号值,如果某个门的输出值发生了变化就继续下一轮迭代,直到所有输出值都稳定下来或者达到最大迭代次数一百次,这种实现方式虽然简单直观并且对小型电路能够正常工作,但效率相对较低并且需要人为设定迭代上限。
通过这次作业我体会到了继承机制在代码复用方面的价值,五个子类的代码总量不到五十行却实现了五种不同的逻辑功能,绝大部分公共逻辑都被复用到了父类中,同时getOrCreatePin方法的臃肿也提醒了我一个方法只做一件事的重要性,在后续的作业中应该有意识地将复杂方法拆解为多个职责单一的小方法。
下图是第四作业的SourceMonitor生成的指标报表截图(four):
0e0ff4df7816aae599b587b0eaa480e7
cc9a51eed1de4d559355f516822486d8
下图是作业四题目自身提供的类图:
e3e76016ed70bcb6e16191b97bce4cf0
2.2 作业集5:复杂元件与架构重构
作业集5在架构层面进行了较为全面的调整,首先引入了ComponentType枚举来统一管理所有元件类型,这样就替代了作业集4中硬编码字符串类型的做法,然后创建了Component抽象类作为所有元件的统一父类,这个类持有名称、类型、输入引脚映射表、控制引脚映射表、是否有输出标志以及输出值等属性,与作业集4相比这次将控制引脚从普通引脚中独立了出来,专门用来管理三态门的使能端和译码器的选择端这类特殊引脚。
九个具体元件类当中五种基本门电路的实现与作业集4类似,新增的三态门引入了高阻态的概念,当控制引脚为高电平时输出等于输入否则输出无效,译码器实现了三线译码器的功能并且固定了使能条件,输出的是选中通道的编号,数据选择器根据控制引脚的值从多个数据输入中选择一个进行输出,数据分配器则将单一输入信号根据控制引脚的值路由到对应的输出通道,它的输出是一个数组而不是单个数值。
作业集5在信号传播机制上做了比较大的调整,作业集4中信号值存储在引脚对象内部并通过引脚间的source引用链逐级获取,而作业集5采用了全局信号值映射表加上独立传播阶段的做法,也就是先计算所有组件的输出值存入映射表,再将映射表中的值分发到所有目标组件的输入引脚,这种计算与传播两阶段分离的设计使得整个逻辑更加清晰。
不过作业集5也存在一些架构方面的不足,Main类承担了过于繁重的职责,包含了输入解析、对象创建、连接建立、信号传播、结果输出等全部功能,createComponent方法使用了九个case分支并且每个分支内还有参数解析和对象创建的逻辑,圈复杂度达到了二十四,属于比较典型的上帝方法反模式,此外大量使用静态方法也说明职责没有合理地分配到组件类自身。
通过这次作业我认识到了架构设计中的取舍问题,统一组件抽象这个方向是对的,但Main类过于臃肿的问题提醒了我类的职责分配需要均衡,不能把所有逻辑都塞进一个上帝类里面,同时三态门和译码器的实现也让我体会到理解领域知识是写出正确代码的重要前提。
下图是第五作业的SourceMonitor生成的指标报表截图(five):
de1cbf637dcc0aabe6b66ac0348d9265
d16c0d26770597a297d8b685b7788ae1
下图是作业五题目自身提供的类图:
715ef385531e728ec638abfa3b16f47d
2.3 作业集6:子电路与异常处理
作业集6在架构上进行了第三次调整,引入了组合设计模式来实现子电路的层次化嵌套,SubCircuitDef类存储子电路的定义信息也就是编号、输入引脚列表、输出引脚列表和内部连接列表,SubCircuitInstance类存储子电路的实例信息也就是编号、输入输出名称集合、输入输出信号映射表以及实例内部包含的所有组件列表,当需要实例化一个子电路时系统会根据定义创建对应的信号对象然后递归创建内部所有组件并建立它们之间的连接关系。
Component类在作业集6中被大幅简化了,它不再持有引脚映射表而是直接持有固定长度的输入值数组和一个输出信号对象,每个组件的计算逻辑比较清晰,先检查所有输入是否都已就绪,如果是则根据类型执行逻辑运算并将结果写入输出信号,这种设计让组件的状态更加明确,输入是数组而输出是信号对象。
Signal类是作业集6中最重要的新增部分,每个Signal对象持有一个整数值和一个目标列表,当值发生变化时setValue方法会将自身加入全局信号队列,propagate方法遍历目标列表并对每个目标调用setValue方法,如果目标是组件的输入引脚就更新对应的输入值并在该组件所有输入就绪时计算输出并触发输出信号的变化,这样就形成了链式反应,一个信号的变化会逐级传播到所有下游元件,这种事件驱动模型较好地解决了前两次作业中需要多次迭代的问题。
五类异常检测在解析阶段完成,具体包括检测连接行引脚数量是否超过两个、单引脚连接中引脚角色的正确性、两引脚连接中输出与输入的顺序是否正确、以及同一输入引脚是否被多个信号源驱动,检测到异常时会立即输出错误信息并终止程序而不进入计算阶段。
从代码度量数据来看作业集6的最大圈复杂度降到了十五,相比作业集5的二十四有了比较明显的改善,这说明职责拆分取得了一定的成效,方法数量增加到了五十个,平均每个方法大约十三行代码,粒度相对更加合理了。
通过这次作业我真正理解了组合模式的威力,通过让子电路和基本元件对外表现一致,系统可以无限层次化地扩展而无需修改已有的代码,BFS事件驱动模型也让我体会到了正确的数据结构往往能够带来更加高效的算法实现。
下图是第六作业的SourceMonitor生成的指标报表截图(six):
ffab7534c0dee712cdf608af9dfd2e6f
222a5d3c5264fa805ad088df20b6c153
下图是作业六题目自身提供的类图:
b12ec83130eac9e8da8917797d98a516
三、采坑心得
作业集4中固定点迭代的收敛性问题出现在一个五级门电路级联的场景中,最后一级门的输出始终为空值即使迭代次数已经达到了一百次的上限,经过调试发现这是因为信号传播需要经过五轮迭代才能从输入端到达输出端,而每次迭代中门电路的计算顺序是固定的列表顺序,如果输出门在列表中排在输入门之前就需要额外一轮迭代来传播已经计算好的信号,对于某些电路拓扑结构固定点迭代甚至可能永远无法收敛,这个问题让我认识到固定点迭代对计算顺序是敏感的,不同的门排列顺序会影响收敛速度甚至收敛性,正确的做法应该是进行拓扑排序并按照信号流向顺序计算。
作业集5中数据分配器的多输出问题比较突出,数据分配器的compute方法体是空的,所有输出值都通过getOutputs方法直接计算返回,这个设计导致数据分配器的输出无法进入统一的信号传播框架,其他组件的输入引脚无法接收数据分配器的输出信号因为数据分配器根本就没有输出信号对象,最终的处理方式是在输出阶段对数据分配器做特殊处理,也就是遍历组件列表时单独判断类型如果是数据分配器就调用其getOutputs方法直接打印结果,这实际上是在业务逻辑中硬编码了对特定类型的特殊处理违反了开闭原则,这个教训让我认识到在设计阶段就应该明确一个元件是单输出还是多输出,对于多输出元件更好的设计方案是让每个输出引脚对应一个独立的Signal对象从而能够融入统一的传播框架。
作业集6中先执行后检查的顺序错误出现在解析子电路定义的过程中,代码先创建了SubCircuitDef对象并填充了部分数据然后才调用detectException进行异常检测,当检测到异常时虽然打印了错误信息并设置了hasError标志但已经创建的对象仍然留在了subDefs映射表中,虽然这个Bug不影响最终的输出结果因为程序在检测到异常后会跳过计算阶段,但它违背了先验证后执行的基本原则,在更复杂的系统中这种顺序错误可能会导致资源泄漏或状态污染,正确的做法应该是在解析连接关系之前就完成所有合法性检查确认无误后再创建任何对象。
作业集5中引脚编号的映射混乱问题也比较典型,不同元件的引脚编号规则存在差异,译码器的控制引脚为零一二而数据引脚从三开始,数据选择器的控制引脚从零到控制数量减一而数据引脚从控制数量开始,数据分配器的控制引脚从零到控制数量减一而数据引脚就是控制数量,这种差异性导致setPinValue方法中需要针对每种类型做特殊映射使得代码比较臃肿且容易出错,在设置译码器的数据引脚时需要计算实际的数据索引也就是pinNum减去三然后判断是否小于输入数量,而在设置数据选择器时需要区分pinNum是否小于控制数量,这种逻辑散落在setPinValue方法中使得代码难以维护,这个教训告诉我接口设计的一致性是非常重要的。
四、改进建议
在架构方面当前三次作业都采用了单类总管的方式,Main类或Circuit类承担了过多的职责,后续可以考虑引入分层架构也就是将解析层、逻辑层和输出层分离开来,解析层负责语法分析和对象创建,逻辑层负责信号传播和计算,输出层负责格式化结果,可以应用策略模式来实现不同元件的创建逻辑并通过工厂注册表实现类型到创建器的映射,观察者模式也可以引入来实现信号变化的自动通知,这实际上是作业集6中Signal机制的一个演进方向。
在代码质量方面作业集5中createComponent方法的圈复杂度为二十四应该将其拆解为多个独立的方法,每种元件的创建逻辑可以封装到各自的工厂类中并通过统一的接口对外提供服务,异常处理方面应该定义业务异常类的层次结构来区分格式错误、语义错误、计算错误等不同类型,并允许调用者在捕获异常后有机会进行恢复或重试而不是直接终止程序。
在可持续改进方面建议建立JUnit单元测试体系为每个元件类编写独立的测试用例覆盖正常计算、边界条件和异常输入等场景,针对信号传播算法可以编写性能测试来验证在大规模电路下的响应时间,完善Javadoc注释来说明每个类的设计意图和每个方法的职责以及前置条件,这样可以降低后续维护时的理解成本。
五、总结
通过这三次作业我在技术层面掌握了接口与抽象类的协作设计、继承体系下的代码复用、枚举类型的使用、工厂模式的雏形、组合模式的应用以及事件驱动模型的实现,三次作业从不同的角度展现了面向对象设计原则在实际开发中的价值。
在工程层面我认识到了输入验证的重要性也就是必须先验证后执行,边界测试的必要性体现在一个简单的超载场景就能暴露出严重的逻辑缺陷,代码可读性的价值在于阅读代码的时间往往远多于编写代码的时间,在领域知识方面我学习了数字逻辑电路的基本概念包括与门、或门、非门、异或门、同或门、三态门、译码器、数据选择器、数据分配器以及信号传播的基本原理,这些知识虽然经过了简化但为后续学习计算机组成原理打下了基础。
需要加强的地方包括设计前瞻性的把控能力,既不能过度设计也不能设计不足,异常处理机制的正确使用需要在捕获异常后提供恢复机会而不是直接退出,测试覆盖的全面性要求不仅要测试正常流程更要覆盖边界条件和异常情况。
三次作业虽然规模不大但涵盖了从基础面向对象编程到专业领域仿真多个层次的知识内容,我不仅学到了Java编程的相关技术也理解了软件工程中的一些基本原则比如单一职责、开闭原则、先验证后执行,这些原则在书本上读起来比较容易理解但在实践中真正领会需要经过不断的试错和反思,作业集6中子电路定义的递归处理让我体会到好的设计能让复杂的系统变得容易理解,而作业集5中Main类的臃肿则让我明白了不好的设计会让简单的系统变得难以维护,这两方面的经验都具有同样的价值。

posted @ 2026-06-25 00:00  鱼15547u  阅读(4)  评论(0)    收藏  举报