第二次blog作业(Java)

一、前言:

· 本次作业集围绕数字电路模拟系统展开迭代:从基础元件到复杂元件如数据分配器,再到异常输入检测和子电路的添加,难度逐渐加大。

· 整体做下来,感觉前两次难度中等,在规定时间内还是绰绰有余。第三次难度徒增,在规定时间要完成真是艰难。

· 这题目集给我最大的感受就是类设计的关键,类与类之间的关系很需要深思熟虑,这一次题目最大部分时间花费反而不在业务处理,而是分类。


· 题目涉及到的相关内容简述:

(1)、 电路中包含与门、或门、非门、异或门、同或门五种元件。元件特征如下:

· 与门: 包含两个或多个输入引脚和一个输出引脚。所有输入引脚必须都是高电平,输出引脚才是高电平,只要有一个输入引脚为低电平,输出引脚输出低电平。
· 或门: 包含两个或多个输入引脚和一个输出引脚。所有输入引脚必须都是低电平,输出引脚才是低电平,只要有一个输入引脚为高电平,输出引脚输出高电平。
· 非门: 包含一个输入引脚和一个输出引脚。输出引脚的电平与输入引脚的电平相反,如输入为低电平,输出则为高电平。
· 异或门: 包含两个输入引脚和一个输出引脚。当两个输入引脚电平不一致时输出引脚输出高电平,否则输出低电平。
· 同或门: 包含两个输入引脚和一个输出引脚。当两个输入引脚电平一致时输出引脚输出高电平,否则输出低电平。

(2) 在第一次元件的基础上添加:

· 三态门: 三态门的作用类似于电路中的开关。包含一个输入引脚、一个输入控制引脚、一个输出引脚。当控制引脚为高电平时,三态门输入输出之间导通,输出电平等于输入电平;当控制引脚为低电平时,三态门输入输出之间呈现高阻态(类似开关断开),输出为无效状态。
· 译码器: 译码器的作用是讲输入的编码转换为一路有效信号。一个译码器包含两个或多个输入引脚(如图中的A2\A1\A0)、三个控制引脚(如图中的S3\S2\S1)、4个或多个输出引脚(如图中的Y7~Y0)。当控制引脚当S1 =1,S2 +S3 =0时,译码器正常工作,输出引脚只有一个输出信号0,其余输出为1;哪个引脚输出0由输入引脚的编码决定。
· 数据选择器: 数据选择器的作用是从多路输入信号中选择一个,并将其信号直接送往唯一的输出端,选择哪一路输入信号由控制端决定。
· 数据分配器: 数据分配器的作用与数据选择器正好相反,是将唯一的一路输入信号输出到多路输出引脚的其中之一,选择哪一路输出引脚输出由控制端决定。

(3) 在第一次基础元件的基础上添加:

· 子电路: 可以将一部分电路设定为一个子电路,子电路可以在主电路中被引用。
· 异常输入信息: 当出现各类异常输入情况时,电路应输出相应提示。

异常情况包括:
1.一个连接信息中包含两个或多个输入
2.一个连接信息中没有输入信息
3.一个连接信息中没有输出
4.一个连接信息中输入输出写反
5.一个输入引脚接受中来自多个不同输出的信号
注:如果一条输入出现了多种异常,按以上异常先后顺序为优先级,顺序靠前者优先级越高,最后输出优先级最高的异常输出。
当电路输入信息中多条输入都包含异常,只处理排在最前面的异常信息。


二、设计与分析:

1、第一次题目集:

( 一 ) 设计过程如下 :

· 首先我观察这套模拟的总组成 :电路由各种元件相接构成,电平的传递在此完成;

· 再看元件是怎么传递电平,以及组成上的异同点 的:元件通过引脚传递电平,经过元件的电平根据元件种类不同输出有异。每种元件都包含引脚,引脚根据电平流出还是流进该元件分为输出引脚和输入引脚。而不同的元件引脚数量有差异。

· 那么据上两点,就可以大致抽象出这套模拟必要的类(冒号后是对该类的细致分析):

(1)Circuit:

· 由于至于扩展提示说到后续可能增加带有反馈性的电路,所以我查阅了资料,发现电路有包含反馈的电路以及普通的逻辑电路,所以我把circuit的设置为了抽象类,让普通逻辑电路CombinationLogicCircuit来继承这个抽象类,作为一个实体类。这样后续直接添加其他类型电路即可。

· 各种元件组成电路,电路上的元件有序排列传递电流。那么电路就会有控制信号在元件间传递的功能,从而抽象为方法ControlSignalTransfer

(2)Component:

· 元件肯定是抽象类,同样也是由于拓展之后会添加其他类型的元件,第1次都是门类型,所以我让门类gate继承它,同样门类也不能实例化,是抽象类,这次的元件又继承gate:AND,OR,NOT,OR,XNOR

· 前面分析到的元件的共性就可以在component里面加,即:两种不同引脚的数组以及不同引脚的数量作为成员属性。

· 题目要求我们输出电路上可以工作原件的输出情况,一个元件是否能正常输出,取决于这个元件的输入是否正常,也就是是否能正常工作,那么这里元件就都应该有一个判断规则。在可以正常工作的基础之上,元件怎么计算出它的输出电平,这也可以抽象出一个方法。不同元件组成不同,判断和计算有异。综上:可以在抽象类中定义抽象方法canWorkCalculateOutputprintOutput,让子类重写即可。

· 再细看信号如何传递,我想要知道一个原件是否能正常工作,主要看他的输入是否有有效电平,易知一个元件的输入来源于上一个元件的输出或者是直接连接信号源,那么我每计算一个元件,如果此时无有效的电瓶,我就可以尝试计算它连接的上一个元件的输出,因此我在总体的计算这个方法里面用的是递归。

· 这里提到它的信号来源有两种,由于判断是哪一种非常不方便,我们可以将可以提供信号来源的部分,抽象为一个提供信号的接口,统一进行管理。
(3)Pin:

· 之前分析提到引脚根据电瓶的流向不同而分成输出引脚和输入引脚,所以我这里也将引脚作为抽象类,让OutputPinInputPin继承它。


· 大概类清晰后,开始关注数据读入的形式:

1 . 输入的数据包括连接信息和输入信号的信息,格式明显不同,这里就需要两个处理输入的不同方法。

2 . 由于最后输出是按编号排序,所以也需要一个排序的方法,由于排序不属于任何现有类的行为,所以单独抽象为了一个Sorter类。

( 二 ) 对源码的分析 :
image

image

image

image

· 可取之处:

1.方法粒度细(平均语句数 3.70):绝大多数方法只做一件简单的事,符合单一职责原则。

2.平均圈复杂度低(平均复杂度 2.12):通常复杂度 < 5 为简单函数,平均值 2.12 意味着大部分方法逻辑清晰,没有过多的 if-else 分支。

3.类的分配合理(19个类,平均每类 5.74 个方法):类数量适中,没有把太多逻辑塞进一个类里。

· 需要留心:

1.最大圈复杂度 21(阈值通常建议 ≤ 10):这说明存在一个非常“致命”的方法,包含了大量嵌套的 if-else、for 循环和逻辑运算符(大概率是 addressLinkedMassenge 或 controlSignalTransfer)

2.分支语句占比 17.9%:这个值不算特别高,但结合最大复杂度 21 来看,说明复杂逻辑集中在了某 1~2 个函数中,而其他函数几乎没有分支。代码分布不均。

3.注释率 12.6%(偏低):影响可读性


2、第二次题目集:

( 一 ) 设计过程如下 :

· 第2次在第1次的基础元件上增加了引脚添加了控制引脚,我在Component中新增了属性:控制引脚数组和其数量。

· 引进了控制引脚就会导致引脚的编号会发生变化,新增的元件引脚编号规则与门类大不相同。所以我在抽象元件类这里增加了一个初始化引脚的抽象方法,每一种元件都去重写,创建该元件时将所有引脚数量、编号都初始化完成。

· 根据引脚编号的不同就可以判断其该元件是否有控制引脚,在处理连接信息等上作了if-else分支处理。

· 第二次新增加的元件有的变化大体在我第一次设计到的可拓展范围内,所以大体写起来就比较顺畅。(不白费我第一次花了大量时间放在分“类”上(●'◡'●))。

( 二 ) 对源码的分析 :

image

image

image

· 可取之处:

1.方法粒度细(平均语句数 0.93):每个方法只做最单一的一件小事,符合“小方法”原则。

2.类的内聚性高(平均方法/类 12.78):每个类包含了足够多的相关方法,没有出现“上帝类”,类职责较集中

3.继承体系扩展性好:新增了 ControlPin、三态门、选择器、译码器等类,没有破坏原有设计

· 需要留心:

1.最大圈复杂度 46(阈值通常建议 ≤ 10):代码中存在一个极复杂的方法,大概addressLinkedMassenge,包含了大量嵌套条件、循环和复杂逻辑。后果:几乎无法单元测试,任何修改都可能引发不可预知的Bug。该方法的可读性极差,维护成本极高

2.最大嵌套深度 8(健康阈值 ≤ 4):同一方法中 if / for / if 层层嵌套达到了8层,极易产生逻辑错误。

3.分支语句占比 24.0%:相比之前(17.9%)有所上升,说明新增的复合元件(MUX、DMUX、Decoder)引入了更多分支判断,但集中度仍然过高。

4.方法调用次数 296:大量方法调用表明代码被拆得非常细,但也可能过度拆分,导致追踪业务流程变得困难


3、第三次题目集:

( 一 ) 设计过程如下 :

· 第3次题增加了异常判断,我专门用一个类errorhandler,里面都是判断各种异常的方法放在读取信息后调用。

· 还一个是子电路的增加(真是让人头疼的存在),这里有一个关系比较复杂,我也捋了很久,(虽然最后这个设计还是没有写好(ㄒoㄒ)。就是主电路是可以包含数个子电路的,主电路可以跟子电路的输入输出打交道,同样子电路也可以跟子电路的输入输出打交道。这给我带来的感觉就是这里的子电路其实相当于是元件了,我本来是想着按照这个相似点,放到元件那一栏的,但是他确实是属于电路啊,(我就很纳闷。)后面给了一个设计提示,我才惊呼应把它抽象成了一个接口,就是相当于跟元件扯上共性的接口!不过由于这个提示出现之前我的大致框架已经弄好了,然后也没有时间再去大改代码(快到截止时间了。。),于是乎,只是加了接口Composite而已。

· 子电路除有输入信号外还有输出信号,我就在原来InputSignal基础上加了OutputSignal

· 由于子电路的出现,在处理控制信号传递上就有点麻烦,如果死板直接先算子电路,再算主电路的话,就有个混乱的问题,按照我之前递归的想法,我子电路也有可能会追溯到主电路上去,然后主电路又追溯到子电路,非常诡异。但是我一时半会想不到递归以外的想法了(哎),所以就(稀里糊涂的处理了一下)--->把递归一开始的参数元件改成那个接口,然后用if else分支判断这个接口是元件类型还是子电路类型,然后分开处理。

( 二 ) 对源码的分析 :

image
(这个整体类图字太小了,下面我裁成左右了,可以对照看下:)

image

image

image

image

image

· 可取之处:

1.方法粒度较细:平均每个方法仅 2.33 条语句,大多数方法只做一件事,符合单一职责原则,易于理解和测试。

2.注释率 12.4%:虽然不算高,但关键部分(如构造、递归、复杂匹配)有简要说明,有助于后续维护。

· 需要留心:

1.最大圈复杂度 41(阈值 ≤ 10):存在一个很复杂的方法(极可能是 addressLinkedMassenge 或 controlSignalTransfer),包含大量嵌套条件和重复代码。后果:难以测试、修改易引入 Bug,维护成本极高。

2.平均复杂度 3.10:虽在可接受范围,但被极值拉高,整体逻辑分布不均,多数方法简单,少数方法过重。


三、踩坑心得

· (1) 第1次题目集是卡在默认,第1条就是input信息,然后调用处理方法的顺序就定死了。导致有个测试点一直没过去。在之前我一直在想“题目没规定就不是规则”的意思,实在想不明白问了同学才知道。我以为imput+链接信息 +OUTPUT +end是输入固定形式(天呐)。

· (2) 第二次题目集也是一个测试点卡了好久,这是是判断上的问题。因为第一次的基础元件canwWork方法要求元件的所有输入都有效,这次新增的元件我是复制的上次的canWork,在此的基础上添加控制引脚的有效性检查。但是数据选择器的输出只需要一个有效电平,即通过控制引脚电平计算得出的对应的引脚编号,就不需要全部输入引脚有效。这也是我问了同学才知道的,在此之前我向各大平台寻求了帮助,均无果。。。差点就放弃了。

· (3) 第三次题目集没有通过全部的测试点,改到后面真的无从下手了...也没有方向...不过应该是我设计结构上没有处理好,但我没时间重构了,所以只是修改再修改,无果。这里就提中间修改到的点,就是我在处理链接信息根据输入的类型不同分了类,子电路与子电路之间打交道这个点我就没处理,直接默认了主电路跟子电路流通,导致了非零返回答案错误等问题。


四、改进建议

其实做下来我感觉题目出的特别好啊,将物理电路放进面向对象这一课题,加深面向对象的理解,更觉世界万物皆可面向对象🤩,这大概是一种魅力。谅我才疏学浅,目前暂无改进建议,只希望可以多来点这种有挑战有意思的题目哈哈!


五、总结

· 回顾这三次迭代,从最初的门电路到复合元件,再到异常检测和子电路,就像坐了一趟过山车——前两次稳稳当当,第三次直接俯冲,刺激又上头🤯。最大的收获不是学会了怎么算电平,而是真正体会到了 “类设计” 的分量:一个好的继承体系能让新增功能水到渠成(比如第二次的三态门、选择器),而一次欠妥的抽象(比如第三次子电路主电路这一块)就会让后续扩展举步维艰。

· 当然,踩过的坑都是宝贵的财富:默认输入顺序的误会、canWork复制粘贴导致的偏差......每一个 bug 都在提醒着我,丰富着我。感谢这套题目把我从面向对象的浅滩冲向更深的水域,虽然游得狼狈,但回头一看,还是收获满满,最后,希望以后还能遇到这种“痛并快乐着”的题目,也期待自己能交出更优雅的设计答卷——毕竟,万物皆可面向对象😛

posted @ 2026-06-24 10:38  wuuccin  阅读(3)  评论(0)    收藏  举报