一、前言
- 家居强电电路模拟程序-3
- 知识点:所考察的知识点与前两次任务相比区别不大,都是主要用到封装,继承和多态等面向对象的基础操作,所用类的结构也没有多大变化,主要是新增了互斥开关以及电路中可能存在多条并联电路,这要求我对电路的相应知识有更多的了解。
- 题量分析:需要识别控制命令和电路的关系,将两者分开识别解析,能够正确的处理电路中的包含关系。
- 难度分析:本题的主要难点有两个,一是新加入的互斥开关,二是串联电路中包含了多条串联电路。互斥开关作为一种特殊的电路元件,具有三个引脚,这意味着它的状态有两种,且电阻变化多达四种,其复杂性在于引脚可以连接在不同的电路上,这大大增加了状态判断和电阻计算的难度。为了应对这个问题,我考虑将互斥开关拆分为两个独立的组件进行计算和判断,虽然这种方法增加了计算的复杂性,但能够确保每个引脚的状态都被准确模拟。另一方面,多条串联电路的存在也很难解决,在我之前的代码设计中,如果一条串联电路包含其他电路,很容易干扰到输入源的识别,从而影响到各个设备的输出电压的正确计算。
- 家居强电电路模拟程序-4
这一次出现了另一个麻烦的元器件,题目要求增加二极管元件,其电路特性为:正向导通,反向截止。在处理二极管时,需要仔细考虑其正向和反向的电流流向,以及它在不同电压下的导通和截止状态,这对电路模拟算法提出了更高的要求。同时,题目还要求在并联电路中再包含并联电路,这无疑增加了电路的复杂性和层次性。在之前的并联电路处理中,我已经需要关注各个并联分支的电流分配和电压相等性,而现在,当并联电路内部再嵌套并联电路时,我们不仅要考虑外部并联分支的电流和电压,还需要深入到内部并联电路中,对每个分支进行细致的分析和计算。这要求处理部分的算法不仅要具备处理复杂电路结构的能力,还要能够准确模拟和计算各个电路元件之间的相互作用和影响。说实话,这一次的难度确实太大了,由于第七次作业的问题都没有很好的解决,导致这一次更加做不出来,我初步考虑二极管可以在引脚上下手,但实际还未处理成功。
二、设计与分析
-
家居强电电路模拟程序-3
UML类图
Device 是一个抽象类,它定义了所有设备的基本属性和方法。例如,identifier、pin1 和 pin2 属性,以及 getOutput 方法。
ControlledDevice 也是一个抽象类,继承自 Device,增加了 toggle 方法。
Fan、Curtain、LFan、IncandescentLight 和 FluorescentLight 都是具体的设备类,它们都继承自 ControlledDevice。这些类实现了 getOutput 方法,并且每个类都有自己的构造函数。
SeriesCircuit 和 ParallelCircuit 分别表示串联电路和并联电路。它们都继承自 Device,并且包含了一个 devices 属性,用于存储电路中的设备。
这些电路类也实现了 getOutput 方法,用于计算电路的输出电压。
DeviceController 是一个控制器类,它管理多个设备和电路。它包含了 seriesCircuits 和 parallelCircuits 属性,分别用于存储串联电路和并联电路。它还包含了一些方法,如 addDevice、removeDevice 等,用于管理和操作设备。
各类关系:包括继承关系、关联关系和依赖关系。例如,DeviceController 与 SeriesCircuit 和 ParallelCircuit 之间是关联关系,而 SeriesCircuit 和 ParallelCircuit 与 Device 之间是聚合关系。
生成报表:
通过图表可以看出:最复杂的方法在第 349 行,为Curtain.addDevice(),最大复杂度为 92。
Line Number of Deepest Block: 最深的代码块在第 493 行
Maximum Block Depth: 最大代码块深度为 9+
Average Block Depth: 平均代码块深度为 4.44
Average Complexity: 平均复杂度为 5.03
这个文件包含了大量的代码,并且有较高的分支语句比例。文件中有较多的注释(21.2%),这有助于代码的可读性和维护性。平均每个类有 6.60 个方法,平均每个方法有 7.30 条语句,说明代码结构较为合理。最复杂的方法是 Curtain.addDevice(),其复杂度为 92,位于第 349 行。代码块的最大深度为 9+,平均深度为 4.44,表明代码有一定的嵌套层次。 -
家居强电电路模拟程序-4
UML类图
在此前的基础上加入了二极管类
分支语句占总语句的25.8%,这表明代码中有较多的条件判断逻辑。从图中可以看出,深度为3和4的代码块最多,分别有约80和60条语句。这表明代码的主要部分集中在中等深度的代码块中,整体结构较为清晰。注释比例较高,方法数量适中,复杂度在合理范围内。
三、踩坑心得及改进
- 第七次作业
- 在之前的代码中,我判断主电路的逻辑是找到最后一个串联电路,没有考虑顺序问题
int seriesNumber = Integer.parseInt(identifier.substring(1)); // 提取编号数字lastSeriesCircuitNumber = Math.max(lastSeriesCircuitNumber, seriesNumber);
在这里找到lastSeriesCircuitNumber,是最后一条串联电路(而我判断最后一条电路的逻辑是找到编号最大的串联电路),然后在finalizeLastSeriesCircuit()添加主电路的方法中,将编号为lastSeriesCircuitNumber的串联电路加在主电路中,这在前两次的代码中没有问题,因为不涉及到乱序问题,而这一次有一些样例,例如:
可以看到在这个样例中,T3是主电路,但是在这之前还有T4、T5电路,导致主电路判断错误。
经过以下修改:
if (line.contains("VCC")) {//mainCircuit = newCircuit; // 更新主电路为当前序列电路lastSeriesCircuitNumber = seriesNumber;}
判断以VCC开头的电路是主电路,解决这个问题。
- 互斥开关存在与两个电路中,这两个电路创建时都会创建一个新的互斥开关设备,导致最后计算电压时没有使用正确的设备,为解决这个问题,引入了一个映射来存放所有已经创建过的设备
private Map<String, Device> createdDevices = new HashMap<>(); // 存储已创建的设备
在 createDevice 方法中改进 MutualSwitch 设备的创建逻辑:
else if (deviceId.startsWith("H")) {device = createdDevices.get(deviceId); // 尝试从已创建设备中获取if (device == null) {device = new MutualSwitch(deviceId, pin, ""); // 创建互斥开关createdDevices.put(deviceId, device); // 存储已创建的互斥开关}
}
通过这些改进,确保了同一个MutualSwitch设备在不同的串联电路中引用的是同一个实例,从而避免了重复创建和错误的结果。
*串联电路中的包含
for (String pin : connectionArray) {// 确保引脚格式正确if (!pin.startsWith("K") && !pin.startsWith("F") &&!pin.startsWith("L") && !pin.startsWith("D") &&!pin.startsWith("B") && !pin.startsWith("R") && !pin.startsWith("M") && !pin.startsWith("A") && !pin.startsWith("H")&& !pin.startsWith("S")&& !pin.startsWith("T")) {continue; // 跳过非设备引脚}
我尝试在这里加入串联电路的标识,试图让其作为一个设备被添加到列表中去,但仅仅这样,添加到设备的串联电路是一个空的,不包含任何设备。这个问题我没有解决,没想到办法让这些电路正确包含
- 关于互斥开关
一开始,我设置开关电阻的逻辑仅仅是这样:
public double getR() {if(isBranch1Active == true) {R = resistanceBetween1And2;}return R;
这就导致我在后面调用它时,电阻只会返回5或无穷大。
后来我意识到,互斥开关的电阻应该有四种情况,我没有办法在一个getR中包含所有的情况。通过询问同学的方法,我了解到可以将这个互斥开关拆成两个,将它们分开来判断,但这需要在后期计算时添加很多判断条件。并且我尝试了这个方法后,还是没有解决该问题。
2. 第八次作业
- 判断短路,若短路输出警告
int flag = deviceController.calculateInputV();
添加一个标志位
if(SertotalR == 0)return 1;else {outputV = device.getInputSources().getOutputV();//System.out.println("short circuit error");return 1;}
calculateInputV()的返回值如果为1,说明短路,在最后输出short circuit error
这量次实验中遇到的问题及解决总结如下:
- 原先通过找到编号最大的串联电路作为主电路,但在某些情况下,如主电路编号并非最大时,会引发错误。
解决方案:调整为根据电路是否以“VCC”开头来确定主电路,因为“VCC”代表电源正极,是电路的主要输入点。这一改动确保了主电路的正确识别。 - 引入了一个HashMap来存放所有已经创建过的设备。在创建设备时,首先尝试从映射中获取已创建的设备实例,如果不存在则创建新的实例并添加到映射中。这一改动确保了同一个互斥开关设备在不同的串联电路中引用的是同一个实例。
- 在计算电压时,需要确保正确地处理电路中的各个设备和它们之间的连接关系。但原先的电压计算逻辑存在漏洞,导致在某些情况下电压值计算错误。
四、总结
在完成家居强电电路模拟程序-3和-4的过程中,我对代码结构的设计以及各个极端情况的考虑有了更深入的理解。这两次作业不仅要求我深入理解电路的基本原理,还挑战了我在代码设计和优化方面的能力。
在第三次作业中,我遇到了多个挑战。首先,互斥开关的引入大大增加了电路模拟的复杂性。互斥开关具有三个引脚和四种可能的电阻状态,且其引脚可以连接在不同的电路上,这使得状态判断和电阻计算变得异常困难。为了应对这个问题,我采用了将互斥开关拆分为两个独立组件的方法进行计算和判断,虽然这种方法增加了计算的复杂性,但确保了每个引脚的状态都能被准确模拟。此外,我还发现原先的通过找到编号最大的串联电路作为主电路的方法在某些情况下会出错,因此我调整为根据电路是否以“VCC”开头来确定主电路,这一改动确保了主电路的正确识别。同时,为了处理同一个互斥开关设备在不同串联电路中引用的问题,我引入了一个HashMap来存放所有已经创建过的设备,确保了设备实例的唯一性。
在第四次作业中,二极管的加入进一步提升了电路的复杂性。二极管具有正向导通、反向截止的特性,为了准确模拟二极管的行为,我在代码中增加了对二极管特性的判断和处理逻辑。同时,题目还要求处理包含多层并联电路的复杂结构,这要求我在处理并联电路时不仅要考虑外部并联分支的电流和电压,还需要深入到内部并联电路中,对每个分支进行细致的分析和计算。这一要求大大增加了代码的复杂性和运行时间,但通过不断地调试和优化,我最终成功地实现了对多层并联电路的正确模拟。
在完成这两次作业的过程中,我也深刻体会到了代码可读性和可维护性的重要性。之前几次还不明显,但随着代码量的增加,很容易出现过一段时间就忘记了之前写过的东西,给代码适当的注释必不可少。并且我与一些同学交谈过,听见他们说第七次的代码其实改动的不多,这便是他们代码设计的优越。在我的代码中,每一次添加设备,尤其是涉及到电路的相互包含,总是会出现不少问题,我的设计并没有很好的处理电路之间的联系,这两次作业写的极其艰难。
五、学期总结
这一个学期的作业已经结束,在这八次的迭代中我确实收获了很多。说实在的,我此前从没写过难度这么大的题,基本每一次的题目我都需要很长的时间来慢慢修改。当然,做了这些效果也是显著的,我对java了解增长的飞快,通过切身体会,更加理解了面向对象语言和面向程序语言的不同。在这么多题目的洗礼下,我已经从第一次刚见到题的无从下手到现在学会先规划类图,来理清楚需要的类的关系,虽然我现在对这些设计还不是特别熟悉,设计的也不太好,容易出现每一次迭代都需要在上一次的基础上大概的情况,但总体来说,我还是在这方面有了一些心得。