一、前言
1.家居强电电路模拟程序3
1.1知识点
(1)面向对象编程(OOP)
- 类与对象:需要设计多个类,如设备类(电路设备类)、受控设备类、控制设备类、串联电路类、并联电路类等。
- 继承与多态:受控设备类和控制设备类可以继承自电路设备类,利用继承实现代码复用和扩展。
- 接口与抽象类:可能需要使用接口或抽象类来定义设备的通用行为,如
getVoltage()
,setState()
等方法。
(2)数据结构
- 图论基础:电路可以抽象为图结构,节点表示设备引脚,边表示连接关系。需要理解图的遍历(如深度优先搜索DFS、广度优先搜索BFS)来模拟电路电压分布。
- 树结构:并联电路和串联电路的嵌套关系可以通过树结构来表示。
- 集合与映射:使用
HashMap
、HashSet
等数据结构来管理设备及其引脚的连接关系。
(3)输入输出处理
- 字符串解析:需要解析复杂的输入格式,包括设备信息、连接信息、控制指令、串联和并联电路信息。可以使用正则表达式、字符串分割方法等。
- 文件读取/控制台输入:根据具体实现,可能需要处理从文件或控制台读取输入。
(4)算法设计
- 电路模拟算法:实现电压分配、电阻计算等电路模拟相关的算法,可能涉及基尔霍夫定律(KVL)和欧姆定律(Ohm's Law)。
- 状态管理与更新:根据控制指令更新设备状态,并重新计算电路参数。
(5)异常处理
- 输入验证:确保输入格式正确,处理不合理或边界情况。
- 错误处理:避免因短路等异常情况导致程序崩溃,尽管题目中部分异常情况被忽略,但良好的编程习惯仍需考虑。
1.2题量
题量较大且复杂,主要表现在以下几个方面:
(1)多种输入类型与复杂的输入格式:需要处理设备信息、连接信息、控制命令、串联电路、并联电路等多种输入,每种输入都有特定的格式和解析要求。
(2)复杂的电路拓扑结构:包括嵌套的串联和并联电路,这增加了输入解析和数据结构构建的难度。
(3)严格的输出格式要求:需要按特定顺序和格式输出设备状态,且涉及不同设备类型的多样化输出格式。
(4)状态与参数计算的复杂性:输出信息不仅仅是设备状态,还涉及基于电路模拟的亮度、转速、窗帘打开比例等参数的计算与转换。
(5)高精度与格式转换要求:数值计算需要高精度(使用double类型),输出时需要按特定规则进行截尾和格式化。
1.3难度
(1)高复杂性
- 电路模拟涉及物理知识(电压、电流、电阻等)与编程实现的结合,要求对电路原理有一定理解。
- 输入格式复杂,解析和验证难度较大。
(2)多模块协作
- 各模块(类设计、输入解析、电路构建、计算模块、输出生成等)需要紧密协作,模块间的接口设计需合理。
(3)算法实现
- 电压分配和电阻计算需要设计高效且准确的算法,尤其在处理复杂的并联和串联嵌套电路时。
(4)状态管理
- 控制指令的处理需要实时更新设备状态,并正确反映到电路模拟中,确保输出的准确性。
(5)错误处理与健壮性
- 尽管部分异常情况被忽略,但程序仍需具备一定的健壮性,避免因输入错误导致的崩溃。
2.家具强电电路模拟程序4
2.1知识点
(1)电路分析
串联与并联电路
- 串联电路:电流通过每个元件,电压在各元件之间分配。题目中提到的串联电路信息和设备连接方式要求理解和分析串联电路的特性。
- 并联电路:多个电路分支并联,电压相同,电流在各分支之间分配。题目中包含了复杂的并联电路嵌套结构,需要掌握并联电路的分析方法。
欧姆定律
- 计算电压、电流和电阻之间的关系。题目中多次涉及到电压差、电流限制和电阻的应用,需要熟练运用欧姆定律 V=I×RV = I \times RV=I×R 进行计算。
电压分配与电流计算
- 在复杂电路中,理解如何在不同分支和元件之间分配电压和电流。特别是在包含调速器、开关和二极管等元件的电路中,需要精确计算各元件的电压和电流。
短路检测
- 识别电路中可能导致无限大电流的短路情况。题目要求在检测到短路时输出特定错误信息,涉及到对电路完整性的检查。
(2)设备建模与行为
开关与互斥开关
- 开关:具有开(1)和关(0)两种状态,控制电路中电流的通断。题目中需要模拟开关的输入输出行为以及状态切换。
- 互斥开关:具有多个分支,但每次只能接通一个分支,涉及到分支间的互斥控制和默认状态的设定。
调速器
- 分档调速器:具有固定档位,每个档位对应不同的输出电压比例。需要模拟档位的增减和对应的电压输出。
- 连续调速器:输出电压与档位参数成比例变化,档位参数可以是连续值。涉及到浮点数的精确控制和输出。
受控设备
- 灯具(白炽灯、日光灯):根据电压差决定亮度,涉及亮度与电压的线性或固定关系。
- 风扇(吊扇、落地扇):根据电压差决定转速,涉及转速与电压的线性或分段关系。
- 受控窗帘:根据光照强度调整窗帘的打开比例,涉及多个条件判断和比例计算。
二极管
- 模拟二极管的正向导通和反向截止特性,涉及方向性控制和电流流动的条件判断。
(3)模拟与状态管理
设备状态
- 每个设备(开关、调速器、灯具、风扇等)都有独立的状态或参数(如开关状态、调速器档位、灯的亮度、风扇的转速)。
- 状态的变化需要根据输入的控制信息进行更新和管理。
电压与电流的实时计算
- 根据电路连接和设备特性,实时计算每个元件的电压和电流,确保所有设备的工作状态符合规定。
错误检测与处理
- 过流检测:监控每个设备的电流是否超过最大限制,触发相应的错误提示。
- 短路检测:在电路中存在短路时,立即终止输出并提示错误。
2.2题量
题量较大且复杂,具体表现在以下方面:
(1)输入结构复杂多样:涉及多种设备、复杂的串联与并联连接、嵌套电路结构,要求高效准确的解析和建模能力。
(2)输出格式严格且内容丰富:需要按照特定顺序和格式输出大量设备的状态信息,包含多个参数和错误提示,增加了格式化输出的难度。
(3)动态计算与状态管理:需要实时计算电压、电流,管理设备状态,并在输出前进行多种条件判断和错误检测。
(4)错误处理机制完善:包括短路和过流检测,需在复杂电路中准确识别并及时响应错误条件。
2.3难度
(1)综合性高
- 题目涉及面广,不仅需要熟悉Java编程,还需要理解电路的基本原理和电气知识。
(2)逻辑复杂
- 电路解析和电压计算涉及复杂的逻辑,特别是在处理嵌套的并联电路和递归计算时,容易出错。
- 需要精确地按照题目要求处理各类设备的状态和参数,确保输出格式正确。
(3)数据处理量大
- 输入信息可能包含大量的设备和连接,需要高效的数据结构和算法来管理和处理。
- 需要处理多种输入格式和多层次的电路结构,增加了实现的复杂度。
(4)错误处理要求高
- 需要全面考虑各种异常情况(如短路、过载),并在程序中正确检测和处理,确保程序的鲁棒性。
(5)细节要求严格
- 输出格式要求严格,任何格式上的错误都会导致答案不正确。
- 数值计算需要遵循截尾规则,确保结果符合要求。
二、设计与分析
1.家居强电电路模拟程序3
1.1类的分析与设计
(1)类的分析
CircuitDevice类:是所有电路设备的基类,定义了设备的基本属性和行为。
ControlDevice类:继承自 CircuitDevice
,专门用于表示能够控制电路状态的设备,如开关和调速器。
Switch类:表示一个普通的开关,具有开启(closed)和关闭(turned on)两种状态,控制电路中电流的通断。
MutualSwitch类:表示一个互斥开关,具有两个分支,每次只能接通其中一个分支,确保电路不会短路。
StepSpeedGovernor类:表示一个分档调速器,具有多个离散档位,每个档位对应不同的输出电压比例。
ContinuousSpeedGovernor类:表示一个连续调速器,输出电压与档位参数成比例变化,档位参数可以是任意小数值。
ControlledDevice类:继承自 CircuitDevice
,用于表示受电路控制的设备,如灯具、风扇和窗帘。
Lamp类:继承自 ControlledDevice
,表示灯具,定义了计算亮度的抽象方法。
IncandescentLamp类:表示白炽灯,根据电压差计算其亮度。
FluorescentLamp类:表示日光灯,根据电压差决定亮度。
CeilingFan类:表示吊扇,根据电压差计算转速。
FloorFan类:表示落地扇,根据电压差分段计算转速。
ControlledCurtain类:表示受控窗帘,根据总光照强度和电压差调整窗帘的打开比例。
SeriesCircuit类:表示一个串联电路,管理其中的设备和连接关系,计算总电阻并分配电压。
ParallelCircuit类:表示一个并联电路,管理其中的多个串联电路,计算总电阻并分配电压。
InputHandler类:负责解析和处理所有的输入命令,构建电路的拓扑结构,并管理设备和电路的注册。
OutputHandler类:负责根据电路的状态生成并输出所有设备的状态信息,按照指定的顺序和格式。
Main类:包含程序的入口点,负责读取输入、构建电路、计算电路参数并输出结果。
(2)类的设计
1.2SourceMonitor报表分析
(1)代码复杂度:
- 方法复杂度:平均复杂度为1.25,最大复杂度为3。复杂度较高的方法(如
Switch.updateOutput()
)可能需要进一步的代码审查和优化,以降低复杂性。 - 最大块深度:最大块深度为6,平均块深度为1.62。这表明代码中存在一些较深的嵌套结构,可能影响代码的可读性和可维护性。
(2)代码长度:
- 行数:总行数为905,这表明代码相对较长,可能需要更多的维护工作。
- 方法数量:每个类平均有2.94个方法,这在合理范围内,但如果某些类的方法过多,可能需要考虑重构。
(3)代码注释:
- 注释覆盖率:代码中有19.6%的行包含注释,这个比例相对较低,可能影响代码的可理解性。增加注释可以帮助开发者更好地理解代码意图。
(4)代码覆盖率:
- 分支覆盖率:分支语句的覆盖率为23.7%,这意味着有相当一部分的代码路径可能没有被测试覆盖,这可能影响代码的稳定性和可靠性。
(5)代码结构:
- 类和接口数量:代码中有16个类和接口,这表明代码具有一定的模块化程度。
- 方法调用语句:方法调用语句为161,这可能表明代码中存在较多的交互和依赖。
(6)代码的可读性:
- 平均每方法语句数:平均每方法有7.87条语句,这个数字相对较低,表明方法可能较短,这有助于提高代码的可读性。
(7)代码的可维护性:
- 最复杂方法:
Switch.updateOutput()
方法的复杂度、语句数、最大深度和调用次数都相对较高,这可能表明该方法需要重构以提高可维护性。
综上所述,代码质量整体上还有一些改进的空间。特别是代码的注释覆盖率较低,分支覆盖率不高,以及存在一些复杂度较高的方法。这些因素都可能影响代码的可读性、可维护性和稳定性。建议进行代码审查,增加注释,优化复杂度高的方法,并提高测试覆盖率。
1.3主要代码分析
(1)计算串联电路电阻的代码
1 // 计算串联电路的总电阻 2 public void computeResistance() { 3 double sumR = 0; 4 5 // 检查是否有任何普通开关处于打开状态 6 boolean isAnySwitchOpen = devices.values().stream() 7 .filter(device -> device instanceof Switch) 8 .map(device -> (Switch) device) 9 .anyMatch(switchDevice -> switchDevice.toString().equals("turned on")); 10 11 if (isAnySwitchOpen) { 12 // 如果有任何开关是打开的,则表明串联电路断路,电阻为无穷大,电压差为0 13 sumR = Double.POSITIVE_INFINITY; 14 } else { 15 // 如果所有开关都闭合,则开始计算每个设备的电阻 16 for (Map.Entry<String, CircuitDevice> entry : devices.entrySet()) { 17 CircuitDevice device = entry.getValue(); 18 19 if (device instanceof StepSpeedGovernor || device instanceof ContinuousSpeedGovernor) { 20 // 如果是调速器,跳过,不加上调速器的电阻 21 continue; 22 } 23 24 if (device instanceof ParallelCircuit) { 25 // 如果是并联电路,先计算并联电路的总电阻 26 ((ParallelCircuit) device).computeResistance(); 27 } else if(device instanceof SeriesCircuit) { 28 //如果是串联电路,先计算串联电路的总电阻 29 ((SeriesCircuit) device).computeResistance(); 30 } 31 32 if (device instanceof MutualSwitch) { 33 MutualSwitch mutualSwitch = (MutualSwitch) device; 34 boolean isConnectedProperly = false; 35 boolean foundPin1 = false; 36 boolean foundPin2 = false; 37 boolean foundPin3 = false; 38 39 // 遍历所有连接,查找互斥开关的引脚连接情况 40 for (String[] connection : connections) { 41 for (String conn : connection) { 42 String[] parts = conn.split("-"); 43 String deviceId = parts[0]; 44 45 // 跳过特殊标识符 46 if (deviceId.equals("IN") || deviceId.equals("OUT") || deviceId.equals("VCC") || deviceId.equals("GND")) { 47 continue; 48 } 49 50 String pin = parts[1]; 51 52 if (deviceId.equals(mutualSwitch.getId())) { 53 if (pin.equals("1")) { 54 foundPin1 = true; 55 } else if (pin.equals("2")) { 56 foundPin2 = true; 57 } else if (pin.equals("3")) { 58 foundPin3 = true; 59 } 60 } 61 } 62 } 63 64 // 根据互斥开关的状态和引脚连接情况判断电阻 65 if ((foundPin1 && foundPin2 && mutualSwitch.getState() == 0) || 66 (foundPin1 && foundPin3 && mutualSwitch.getState() == 1)) { 67 // 当互斥开关的连接符合其状态时,使用实际电阻 68 isConnectedProperly = true; 69 } 70 71 // 如果连接符合互斥开关的状态,则使用实际电阻,否则电阻为无穷大 72 if (isConnectedProperly) { 73 sumR += mutualSwitch.getResistance(); 74 } else { 75 sumR = Double.POSITIVE_INFINITY; 76 //break; // 互斥开关不符合状态时,整个电路断路,直接设置为无穷大 77 } 78 79 } else { 80 // 对于普通设备,直接加上其电阻 81 sumR += device.getResistance(); 82 } 83 } 84 } 85 86 // 设置串联电路的总电阻 87 setResistance(sumR); 88 }
功能:计算串联电路(SeriesCircuit
)的总电阻。该方法考虑了电路中各设备的电阻,开关状态以及互斥开关的连接情况。
实现逻辑:
-
检查开关状态:
- 使用流式操作(Streams)遍历电路中的所有开关(
Switch
类)。 - 如果存在任何一个开关处于“turned on”状态(即打开状态),则整个串联电路断路,总电阻设为无穷大。
- 使用流式操作(Streams)遍历电路中的所有开关(
-
计算总电阻(当所有开关都闭合时):
- 遍历电路中的所有设备(排除调速器设备,如
StepSpeedGovernor
和ContinuousSpeedGovernor
)。 - 对于每个设备:
- 并联电路:若设备是
ParallelCircuit
,则递归调用其computeResistance()
方法以获取并联电路的总电阻。 - 串联电路:若设备是
SeriesCircuit
,则递归调用其computeResistance()
方法以获取嵌套串联电路的总电阻。 - 互斥开关(
MutualSwitch
):根据其当前状态和连接情况,决定是否将其电阻计入总电阻。如果互斥开关的连接不符合其状态,则整个电路断路,总电阻设为无穷大。 - 普通设备:直接将设备的电阻累加到总电阻中。
- 并联电路:若设备是
- 遍历电路中的所有设备(排除调速器设备,如
-
设置总电阻:
- 将计算得到的总电阻值通过
setResistance(sumR)
方法设置到串联电路对象中。
- 将计算得到的总电阻值通过
(2)给串联电路中的各个设备分配电压的代码
1 // 给每个电器设备按照电阻大小分配电压 2 public void applyVoltage() { 3 double totalVoltage = getVoltageDifference();//串联电路的总电压差 4 5 // 检查是否存在调速器,并计算其输出电压 6 for (CircuitDevice device : devices.values()) { 7 if (device instanceof StepSpeedGovernor) { 8 device.setInputVoltage(totalVoltage); 9 ((StepSpeedGovernor) device).updateOutput(); 10 totalVoltage = device.getOutputVoltage(); // 使用调速器的输出电压 11 break; // 假设只有一个调速器,找到后即可退出 12 } else if (device instanceof ContinuousSpeedGovernor) { 13 device.setInputVoltage(totalVoltage); 14 ((ContinuousSpeedGovernor) device).updateOutput(); 15 totalVoltage = device.getOutputVoltage(); // 使用调速器的输出电压 16 break; // 假设只有一个调速器,找到后即可退出 17 } 18 } 19 20 for (Map.Entry<String, CircuitDevice> entry : devices.entrySet()) { 21 CircuitDevice device = entry.getValue(); 22 23 if (device instanceof StepSpeedGovernor || device instanceof ContinuousSpeedGovernor) { 24 continue; // 跳过调速器 25 } 26 27 //根据电压给每个电器设备分配电压 28 if(Double.isInfinite(device.getResistance())) {//如果这个设备的电阻无穷大,则等于输入的电压差 29 device.setVoltageDifference(totalVoltage); 30 } 31 else {//如果电阻不是无穷大,按照分压法给电器设备分压 32 double deviceVoltage = totalVoltage * device.getResistance() / getResistance(); 33 device.setVoltageDifference(deviceVoltage); 34 } 35 36 //如果遇到这个设备是并联电路或者是串联电路,那么还需要给这个设备里面的电器设备分配电压 37 if(device instanceof ParallelCircuit) {//如果是串联电路中的并联电路,那么还要调用并联电路的分压方法,给并联电路中各个设备分压 38 ((ParallelCircuit) device).applyVoltage(); 39 } else if(device instanceof SeriesCircuit) { 40 ((SeriesCircuit) device).applyVoltage(); 41 } 42 43 } 44 }
功能:根据串联电路的总电压和各设备的电阻,按照分压法为每个设备分配电压。此方法还处理了调速器的输出电压,并递归应用于嵌套的并联或串联电路。
实现逻辑:
-
获取总电压:
- 从串联电路对象中获取总电压差(
getVoltageDifference()
),即串联电路的总输入电压。
- 从串联电路对象中获取总电压差(
-
处理调速器:
- 遍历串联电路中的所有设备,查找是否存在调速器(
StepSpeedGovernor
或ContinuousSpeedGovernor
)。 - 如果找到调速器,设置其输入电压,并调用其
updateOutput()
方法计算输出电压。 - 将调速器的输出电压作为后续设备的输入电压(假设电路中最多有一个调速器)。
- 遍历串联电路中的所有设备,查找是否存在调速器(
-
分配电压给各设备:
- 遍历串联电路中的所有设备(跳过调速器)。
- 对于每个设备:
- 无穷大电阻:若设备的电阻为无穷大(断路),则其电压差等于总电压差。
- 有限电阻:按分压法计算设备的电压差,即
deviceVoltage = totalVoltage * device.getResistance() / getResistance()
。 - 嵌套电路:
- 若设备是
ParallelCircuit
或SeriesCircuit
,则递归调用其applyVoltage()
方法,进一步分配电压给嵌套电路中的设备。
- 若设备是
(3)解析串联电路信息的代码
1 // 处理串联电路信息 2 private void processSeriesCircuit(String input) { 3 String[] parts = input.split(":"); 4 String circuitId = parts[0].substring(1); // #Tn:提取出电路编号 5 SeriesCircuit seriesCircuit = new SeriesCircuit(circuitId); // 声明一个串联电路的子类 6 Pattern pattern = Pattern.compile("\\[(.*?)\\]"); // 用于捕获连接信息的正则表达式 7 Matcher matcher = pattern.matcher(parts[1]); 8 boolean isMainCircuit = false; 9 10 while (matcher.find()) { // 在串联线路信息中解析出连接信息 11 String[] connParts = matcher.group(1).split(" "); // 将连接信息中的引脚分开 12 seriesCircuit.addConnection(connParts); // 把连接信息保存到串联电路中 13 for (String part : connParts) { 14 if (part.equals("VCC")) { 15 isMainCircuit = true; // 如果连接信息中包含VCC,标记为主电路 16 } 17 String[] deviceInfo = part.split("-"); 18 String deviceId = deviceInfo[0]; 19 if (seriesCircuit.getDeviceById(deviceId) == null) { // 如果没有这个电器设备的话就把它加入串联电路的电器设备表中 20 addDeviceById(deviceId, seriesCircuit); 21 } 22 23 } 24 } 25 26 if (isMainCircuit) { 27 mainCircuit = seriesCircuit; // 如果是主电路,则设置为主电路 28 } 29 30 seriesCircuits.put(circuitId, seriesCircuit); 31 }
功能:解析串联电路的信息,创建 SeriesCircuit
对象,添加设备和连接关系,并确定是否为主电路(连接到 VCC
)。
实现逻辑:
-
解析输入行:
- 将输入行按
:
分割,提取电路编号和连接信息。 - 使用正则表达式
\\[(.*?)\\]
匹配并提取每组连接信息。
- 将输入行按
-
创建串联电路对象:
- 根据电路编号创建一个新的
SeriesCircuit
对象。
- 根据电路编号创建一个新的
-
处理连接信息:
- 对于每个匹配的连接组:
- 分割连接组内的引脚信息。
- 将连接信息添加到
SeriesCircuit
对象中。 - 遍历每个引脚,若包含
VCC
,则标记为主电路。 - 根据设备标识符(如
K1
、F2
等),调用addDeviceById()
方法添加设备到当前串联电路中。
- 对于每个匹配的连接组:
-
确定主电路:
- 如果连接信息中包含
VCC
,则将当前串联电路设置为主电路。
- 如果连接信息中包含
-
注册串联电路:
- 将当前串联电路对象存储到
seriesCircuits
映射中。
- 将当前串联电路对象存储到
(4)输出每个设备状态的代码
1 // 显示电路中所有设备的状态 2 public void displayStatus(SeriesCircuit circuit) { 3 Set<String> visitedIds = new HashSet<>(); 4 List<CircuitDevice> allDevices = circuit.collectAllDevices(visitedIds); 5 6 //更新除了窗帘的所有受控设备的输出 7 updateDeviceOutput(allDevices); 8 9 // 定义设备类型的顺序 10 List<Class<? extends CircuitDevice>> order = Arrays.asList(Switch.class, StepSpeedGovernor.class, 11 ContinuousSpeedGovernor.class, IncandescentLamp.class, 12 FluorescentLamp.class, CeilingFan.class, FloorFan.class, MutualSwitch.class, ControlledCurtain.class); 13 14 // 对所有设备进行排序 15 allDevices.sort(Comparator.comparing((CircuitDevice device) -> order.indexOf(device.getClass())) 16 .thenComparing(CircuitDevice::getId)); 17 18 // 打印设备状态 19 for (CircuitDevice device : allDevices) { 20 System.out.println("@" + device.getId() + ":" + device.toString()); 21 } 22 }
功能:收集所有设备,更新设备的输出状态(如亮度、转速、窗帘打开比例),按照指定的设备类型和编号顺序排序,并格式化输出每个设备的状态信息。
实现逻辑:
-
收集所有设备:
- 调用
collectAllDevices()
方法,递归遍历电路,收集所有设备,避免重复访问。
- 调用
-
更新设备输出:
- 调用
updateDeviceOutput(allDevices)
方法,更新受控设备的输出状态(如亮度、转速)。 - 计算总亮度并更新窗帘的打开比例。
- 调用
-
排序设备:
- 定义设备类型的输出顺序列表(如
Switch
、StepSpeedGovernor
、ContinuousSpeedGovernor
、IncandescentLamp
等)。 - 对收集到的所有设备按照设备类型的顺序和设备编号进行排序。
- 定义设备类型的输出顺序列表(如
-
格式化并输出设备状态:
- 遍历排序后的设备列表,调用每个设备的
toString()
方法获取其状态描述。 - 按照格式
@设备标识:状态描述
输出每个设备的状态。
- 遍历排序后的设备列表,调用每个设备的
2.家居强电电路模拟程序4
2.1类的分析与设计
(1)类的分析
CircuitDevice
类:是所有电路设备的基类,定义了设备的基本属性和行为。它提供了共享的字段和抽象方法,供子类实现具体的功能。
ControlDevice
类:继承自 CircuitDevice
,代表那些可以控制电路状态的设备,如开关和调速器。它们通常具有零电阻(或特定电阻)并能通过用户输入改变状态。
ControlledDevice
类:继承自 CircuitDevice
,代表那些受控制的设备,如灯具和风扇。这些设备的行为依赖于控制设备的状态。
Switch
类:代表一个普通的开关,能够在闭合(closed
)和断开(turned on
)状态之间切换。
MutualSwitch类:代表一个单极双掷开关(SPDT),可以连接到两个不同的输出端(例如,引脚2和引脚3)。
DividerGovernor
类:代表分档调速器,能够在多个预设挡位之间切换,以控制连接设备(如风扇)的输出电压。
ContinuousGovernor
类:代表连续调速器,允许用户设置任意电压比例,以精确控制连接设备的输出。
IncandescentLamp
类:代表白炽灯,根据电压降落(voldrop
)计算亮度。
FluorescentLamp
类:代表日光灯,根据电压降落计算亮度。
CeilingFan
类:代表吊扇,根据电压降落计算转速。
Curtain
类:代表受控窗帘,根据总亮度和电压降落调整打开比例。
DiodeTube
类:代表二极管,根据状态(导通或截止)决定电流和电压。
Circuit
类:负责管理整个电路的设备、串联和并联电路的结构,并执行电阻计算和设备状态更新。
Input
类:负责读取和解析用户输入,根据输入内容创建设备、建立电路连接,并处理控制命令。
Export
类:负责更新电路中所有设备的状态,并按照特定顺序输出设备的状态信息。
Main
类:程序的入口点,负责整体流程的控制,包括读取输入、构建电路、更新设备状态和输出结果。
(2)类的设计
2.2SourceMonitor报表分析
(1)代码复杂度:
- 平均复杂度:平均复杂度为5.57,这表明代码的整体复杂度相对较高。
- 最大复杂度:
Export.print()
方法的复杂度高达97,这是一个警示信号,表明该方法可能过于复杂,需要重构以降低复杂度。 - 最大块深度:最大块深度为6,平均块深度为1.98,这表明代码中存在较深的嵌套结构,可能影响代码的可读性和可维护性。
(2)代码长度:
- 行数:总行数为1,195,这是一个相对较大的代码文件,可能需要更多的维护工作。
- 方法数量:每个类平均有5.85个方法,这个数字相对较高,可能表明某些类承担了过多的职责,需要考虑是否需要重构以提高模块化。
(3)代码注释:
- 注释覆盖率:代码中有12.7%的行包含注释,这个比例相对较低,可能影响代码的可理解性。增加注释可以帮助开发者更好地理解代码意图。
(4)代码覆盖率:
- 分支覆盖率:分支语句的覆盖率为26.1%,这意味着有相当一部分的代码路径可能没有被测试覆盖,这可能影响代码的稳定性和可靠性。
(5)代码的可读性:
- 平均每方法语句数:平均每方法有11.24条语句,这个数字相对较高,表明方法可能较长,这可能影响代码的可读性。
(6)代码的可维护性:
- 最复杂方法:
Export.print()
方法的复杂度、语句数、最大深度和调用次数都相对较高,这可能表明该方法需要重构以提高可维护性。
(7)代码结构:
- 类和接口数量:代码中有13个类和接口,这表明代码具有一定的模块化程度。
- 方法调用语句:方法调用语句为276,这可能表明代码中存在较多的交互和依赖。
(8)代码的健壮性:
- 块深度分布:大部分语句的块深度在0到2之间,这是一个好的迹象,表明代码的控制流结构相对简单。但是,仍然有部分语句的块深度达到了6,这可能需要进一步的优化。
综上所述,代码质量存在一些需要关注的问题,特别是代码的注释覆盖率较低,分支覆盖率不高,以及存在一些复杂度极高的方法。这些因素都可能影响代码的可读性、可维护性和稳定性。建议进行代码审查,增加注释,优化复杂度高的方法,并提高测试覆盖率。特别是Export.print()
方法,由于其极高的复杂度,应作为优化的优先事项。
2.3主要代码分析
(1)计算串联电路电阻的代码
1 //计算串联电路电阻 2 public double calculateResistance(String id) { 3 double resistance = 0.0; 4 List<String> list = seriesCircuits.get(id); 5 int status = judgeSeries(id); 6 if (status == 1) { //串联电路短路 7 return 0.0; 8 } else if (status == -1) { //串联电路断路,包括二极管反向截止的情况 9 resistance = Double.MAX_VALUE; 10 } else if (status == 0) { //串联电路正常联通 11 for (String temp : list) { 12 if (temp.startsWith("M")) { //串联电路中遇到并联电路 13 double resistance1 = parallelResistance(temp); 14 resistance += resistance1; 15 } else if(temp.startsWith("T")) { //串联电路中遇到串联电路 16 double resistance1 = calculateResistance(temp); 17 resistance += resistance1; 18 } else { 19 if(temp.startsWith("H")) { //互动开关存储时有引脚需去掉 20 String[] part = temp.split("-"); 21 if(part[1].contains("1")) continue; 22 else temp = part[0]; 23 } 24 CircuitDevice toDevice = devices.get(temp); //二极管在此处得到的电阻只能是0 25 resistance += toDevice.getResistance(); //为了防止溢出,二极管反向截止的情况会被视为断路 26 } 27 } 28 } 29 // System.out.println(id+":"+resistance); 30 return resistance; 31 }
功能:计算指定串联电路的总电阻。该方法通过递归方式处理串联和并联电路,考虑了电路的状态(正常联通、断路或短路)。
实现逻辑:
- 初始化电阻:将总电阻初始化为0.0。
- 获取串联电路的元件列表:通过
seriesCircuits.get(id)
获取指定串联电路ID的所有连接元件。 - 判断串联电路状态:调用
judgeSeries(id)
方法,返回电路状态:1
:短路-1
:断路(包括二极管反向截止)0
:正常联通
- 根据状态计算电阻:
- 短路 (
status == 1
):总电阻为0.0。 - 断路 (
status == -1
):总电阻为无穷大 (Double.MAX_VALUE
)。 - 正常联通 (
status == 0
):- 遍历串联电路中的每个元件:
- 并联电路 (
temp.startsWith("M")
):调用parallelResistance(temp)
计算并联电路的电阻并累加。 - 子串联电路 (
temp.startsWith("T")
):递归调用calculateResistance(temp)
计算子串联电路的电阻并累加。 - 互动开关 (
temp.startsWith("H")
):处理互动开关的引脚,可能需要跳过某些引脚。 - 普通设备:直接获取设备的电阻并累加。
- 并联电路 (
- 遍历串联电路中的每个元件:
- 短路 (
- 返回总电阻:根据计算结果返回总电阻值。
(2)判断并联电路的状态的代码
1 public int judgeParallel(String id) { 2 int status = 0; // 0-正常 1-短路 -1-断路 3 int break_num = 0; // 断路支路个数 4 String[] connection = parallelCircuits.get(id); 5 for (String conn : connection) { 6 int state = judgeSeries(conn); 7 if (state == 0) { // 有电阻联通 8 // 不改变状态 9 } else if (state == 1) { // 无电阻联通 (短路) 10 status = 1; 11 break; 12 } else if (state == -1) { // 支路断路 13 break_num++; 14 } 15 } 16 if (break_num == connection.length) { // 所有支路都断路 17 status = -1; 18 } 19 return status; 20 }
功能:判断指定并联电路的状态(正常、短路或断路)。
实现逻辑:
- 初始化状态:设定初始状态为正常 (
status = 0
)。 - 初始化断路支路计数:
break_num = 0
。 - 获取并联电路的串联支路:通过
parallelCircuits.get(id)
获取所有串联支路。 - 遍历每个串联支路:
- 调用
judgeSeries(conn)
判断串联支路的状态:- 正常联通 (
state == 0
):继续检查其他支路。 - 短路 (
state == 1
):并联电路被短路,设置status = 1
并退出循环。 - 断路 (
state == -1
):增加断路支路计数break_num++
。
- 正常联通 (
- 调用
- 判断并联电路最终状态:
- 如果所有支路都断路 (
break_num == connection.length
),设置status = -1
(断路)。 - 否则,保持当前状态。
- 如果所有支路都断路 (
- 返回状态:返回并联电路的状态值。
(3)更新电路中所有设备状态的代码
1 //更新设备信息 2 public boolean updateDevices() { 3 boolean flag = false; //返回值,false表示电路正常,继续输出 4 judgeSPDTswitch(); 5 Set<String> KEY = seriesCircuits.keySet(); 6 List<String> array = new ArrayList<>(KEY); 7 Map<String, Double> Resistance_list = new HashMap<>(); 8 for (String i : array) { 9 Resistance_list.put(i, calculateResistance(i)); 10 } 11 String mainCircuit = array.get(array.size() - 1); //记录主干路id 12 double totalresis = Resistance_list.get(mainCircuit); //总电阻 13 double inputVoltage = 220.0; 14 double outputVoltage = 0.0; 15 double current = 0.0; 16 int status = judgeSeries(mainCircuit); 17 if (status == -1 ) { //如果主干路被断路 18 updateSeries(mainCircuit, inputVoltage, outputVoltage ,current); 19 } 20 else if( status == 1) { //如果主干路被短路 21 System.out.println("short circuit error"); 22 flag=true; 23 return flag; 24 } 25 else if (status == 0) { // 说明并联电路没有断开 26 List<String> temp = seriesCircuits.get(mainCircuit); 27 for (String temp2 : temp) { //遍历主干路元器件 28 CircuitDevice device = devices.get(temp2); 29 if (device instanceof DividerGovernor) { 30 DividerGovernor dsc = (DividerGovernor) device; 31 inputVoltage *= dsc.gearRatios[dsc.currentGear]; 32 } else if (device instanceof ContinuousGovernor) { 33 ContinuousGovernor csc = (ContinuousGovernor) device; 34 inputVoltage *= csc.gearRatio; 35 } 36 } //更新inputVoltage 37 current = inputVoltage / totalresis ; //总电流 38 updateSeries(mainCircuit, inputVoltage, outputVoltage, current); 39 updateCurtain(); 40 } 41 return flag; 42 }
功能:更新整个电路中所有设备的状态,包括计算电路的总电阻、分配电压和电流,并更新受控设备(如窗帘)的状态。该方法还会检测是否存在短路错误。
实现逻辑:
- 初始化标志:设定
flag = false
,表示电路正常。 - 判断互动开关方向:调用
judgeSPDTswitch()
方法,确保互动开关的方向正确。 - 计算所有串联电路的电阻:
- 遍历所有串联电路ID,调用
calculateResistance(i)
计算每个串联电路的电阻,并存储在Resistance_list
中。
- 遍历所有串联电路ID,调用
- 确定主串联电路:假设主串联电路为串联电路列表中的最后一个ID。
- 获取总电阻:从
Resistance_list
中获取主串联电路的总电阻。 - 设定输入电压:设定为固定值
220.0
(可以考虑使用常量)。 - 判断主串联电路状态:调用
judgeSeries(mainCircuit)
方法,返回电路状态。 - 根据主串联电路状态执行不同操作:
- 断路 (
status == -1
):- 调用
updateSeries(mainCircuit, inputVoltage, outputVoltage, current)
更新主串联电路。
- 调用
- 短路 (
status == 1
):- 打印 "short circuit error"。
- 设置
flag = true
并返回flag
。
- 正常联通 (
status == 0
):- 遍历主串联电路的所有元件:计算总电流:
current = inputVoltage / totalresis
。- 调速器 (
DividerGovernor
,ContinuousGovernor
):- 根据调速器的挡位或比例,调整
inputVoltage
。
- 根据调速器的挡位或比例,调整
- 调速器 (
- 计算总电流:
current = inputVoltage / totalresis
。 - 调用
updateSeries(mainCircuit, inputVoltage, outputVoltage, current)
更新主串联电路。 - 调用
updateCurtain()
更新受控窗帘的状态。
- 遍历主串联电路的所有元件:计算总电流:
- 断路 (
- 返回标志:返回
flag
,表示是否存在短路错误。
(4)解析输入信息的代码
1 public void processInput() { 2 while (scanner.hasNextLine()) { 3 String line = scanner.nextLine().trim(); 4 if (line.equals("end")) break; 5 if (line.startsWith("#T")) { 6 if(line.contains("IN") && line.contains("OUT")) processSeriesCircuit(line); 7 else if(line.contains("VCC") && line.contains("GND")) processSeriesCircuit(line); 8 } else if (line.startsWith("#M")) { 9 processParallelCircuit(line); 10 } else if (line.startsWith("#")) { 11 circuit.processControl(line); 12 } 13 } 14 }
功能:读取并解析用户输入,创建设备并建立电路连接,包括串联电路、并联电路和控制命令。
实现逻辑:
- 读取输入:通过
scanner.hasNextLine()
循环读取每一行输入。 - 判断输入类型:
- 串联电路 (
line.startsWith("#T")
):- 如果包含
"IN"
和"OUT"
,或包含"VCC"
和"GND"
,调用processSeriesCircuit(line)
处理串联电路。
- 如果包含
- 并联电路 (
line.startsWith("#M")
):- 调用
processParallelCircuit(line)
处理并联电路。
- 调用
- 控制命令 (
line.startsWith("#")
):- 调用
circuit.processControl(line)
处理控制命令。
- 调用
- 串联电路 (
- 结束条件:如果读取到
"end"
,停止读取输入。
三、踩坑心得
1.家居强电电路模拟程序3
1.1互斥开关的创建
由于互斥开关有三个引脚,所以一个互斥开关连接信息中会出现两次,这就导致在解析连接信息时会重复生成同一个互斥开关,导致输出的时候出现两个互斥开关
解决办法:引入一个全局注册表,也就是在解析连接信息时,首先查看注册表中是否有连接信息中的设备,如果有的话就将注册表中设备加入到串联电阻中,如果没有的话就先加入到注册表中,然后再加入到串联电路中。
测试样例:
错误输出:
正确输出:
2.家居强电电路模拟程序4
2.1互斥开关状态切换不正确
切换互斥开关时,可能导致两个分支引脚同时接通或同时断开,违反互斥原则。
解决方法:
- 确保互斥性:在
toggle
方法中,切换状态时应明确只接通一个分支引脚,并断开另一个。例如,使用布尔变量来标识当前接通的是2号还是3号引脚,切换时反转该变量并更新对应的输出电压。 - 更新电压:在切换后,确保2号和3号引脚的电压根据当前状态正确更新,避免短路。
2.2二极管(DiodeTube)方向性处理错误
二极管的正向导通与反向截止状态未正确判断,导致电路模拟不准确。
解决方法:
- 正确判断电压方向:在
updateState
方法中,判断输入电压与输出电压的关系,决定二极管是导通还是截止。 - 处理电压相等情况:根据接入方向(
isNearVcc
),在电压相等时正确设置导通或截止状态。 - 避免使用无限大电阻:在反向截止时,避免使用
Double.MAX_VALUE
,可以引入一个极大的常数(如1e9
)来模拟无限大电阻,防止在计算中引发数值溢出。
2.3串联与并联电路的嵌套处理错误
电路中包含多层嵌套的串联与并联连接时,递归处理逻辑可能出现错误,导致电阻和电压计算不准确。
解决方法:
- 递归调用管理:在
calculateResistance
和parallelResistance
方法中,确保递归调用正确处理嵌套电路,避免遗漏或重复计算。 - 循环连接检测:加入检测机制,防止电路输入中存在循环连接,导致无限递归。例如,使用一个访问标记集合来记录已访问的电路ID,若再次遇到相同ID则抛出异常或返回错误状态。
四、改进建议
1.家居强电电路模拟程序3
(1)减少使用instanceof
- 使用多态方法:在基类或接口中定义抽象方法,让每个子类实现自己的行为。例如,添加一个
computeOutput()
方法,让每个设备自行计算输出。这样,在需要更新设备输出时,可以直接调用computeOutput()
,而无需使用instanceof
。 - 应用设计模式:考虑使用 Visitor模式 或 策略模式 来处理不同设备的行为,进一步减少
instanceof
的使用。
(2)优化电压分配逻辑
- 分离计算逻辑:将电压分配和电阻计算逻辑与电路结构分离,使代码更具模块化和可维护性。
- 处理调速器:将硬编码的数值(如220V、80V等)替换为常量,提高代码的可读性和可维护性。
(3)增强错误处理与输入验证
- 输入验证:当前的
InputHandler
对输入格式有基本的匹配,但缺乏详细的验证和错误反馈。需要添加更多的输入验证,并在输入不合法时提供清晰的错误信息。 - 异常处理:在关键方法中添加异常处理,以防止程序因未处理的异常而崩溃。
- 输入处理中的鲁棒性:确保在处理控制命令时,设备存在且类型正确,避免
ClassCastException
。
(4)提升代码的可扩展性
- 使用工厂模式:为设备的创建引入工厂模式,简化设备实例化过程,并使其更具可扩展性。
- 使用接口:考虑为不同类型的设备定义接口,以便于实现多重继承和提高灵活性。
(5)使用枚举代替整型状态
例如,将开关状态用枚举表示,而不是整型。
2.家居强电电路模拟程序4
(1)使用接口替代部分抽象类
当前,CircuitDevice
是所有设备的基类,ControlDevice
和 ControlledDevice
继承自它。然而,某些设备可能同时具备控制和被控制的特性。考虑使用接口(Interfaces)来更灵活地定义设备的行为。
(2)引入工厂模式
当前,设备的创建逻辑集中在 Input
类的 createDevice
方法中。可以将设备创建逻辑封装到一个独立的工厂类中,遵循单一职责原则。
(3)使用观察者模式处理受控设备
受控设备(如窗帘)依赖于系统的总亮度。可以使用观察者模式,使受控设备订阅亮度变化的事件,而不是在电路更新时手动调用更新方法。
(4)分解大型方法
某些方法如 updateDevices
和 updateSeries
过于庞大,建议将其分解为更小的子方法,提升代码的可读性和可维护性。
(5)统一电压与电流处理
当前设备类中的 updateState
方法有多个重载,处理不同的情境(断路、正常联通、短路)。建议统一方法签名,使用可选参数或额外的标志来指示不同的情境,减少方法重载带来的复杂性。
(6)缓存电路状态
对于大型电路,反复计算电阻和电路状态可能导致性能问题。可以考虑引入缓存机制,缓存已经计算过的电路电阻和状态,避免重复计算。
(7)捕获并处理潜在异常
在电路计算和设备更新过程中,可能会遇到诸如 NullPointerException
、NumberFormatException
等异常。应在关键位置捕获并妥善处理这些异常,避免程序崩溃。
五、总结
1.心得体会
在编写电路模拟代码的过程中,我深刻体会到了面向对象编程在复杂系统设计中的强大优势。通过设计抽象的CircuitDevice
类及其子类,如Switch
、MutualSwitch
、DividerGovernor
和各种受控设备,我学会了如何通过继承和多态有效地组织和管理不同类型的电路元件。此外,处理串联和并联电路的递归计算逻辑,使我更加理解了电学基本原理在编程中的应用,同时也提升了我在算法设计和数据结构选择方面的能力。使用正则表达式进行输入解析,加强了我在文本处理和模式匹配方面的技能。通过实现设备状态的动态更新和错误处理机制,我认识到在模拟真实世界系统时,细致的状态管理和鲁棒的错误处理是确保系统可靠运行的关键。整体而言,这次项目不仅巩固了我在Java编程语言上的知识,还让我深刻理解了如何将物理概念转化为可编程的逻辑结构,为未来设计更复杂和高效的模拟系统打下了坚实的基础。
2.学习计划
(1)深化面向对象编程(OOP)知识
- 深入理解继承与多态:通过更多的项目练习,掌握如何有效地使用继承和多态来设计灵活且可扩展的类层次结构。
- 学习接口与抽象类的最佳实践:了解何时使用接口,何时选择抽象类,以优化代码的可维护性和复用性。
(2)掌握设计模式
- 工厂模式(Factory Pattern):学习如何使用工厂模式来简化对象的创建过程,尤其是在设备类型繁多的情况下。
- 观察者模式(Observer Pattern):应用观察者模式来处理设备之间的状态更新,提高系统的响应性和模块化。
- 单例模式(Singleton Pattern):确保某些关键类(如设备注册表)在整个系统中只有一个实例,避免资源冲突。
(3)增强系统设计与架构
- 模块化设计:将不同功能模块(如设备管理、输入处理、输出处理)分离到独立的包或模块中,提升代码的组织性和可维护性。
- 应用SOLID原则:遵循单一职责、开放封闭、里氏替换、接口隔离和依赖倒置等SOLID原则,提升代码质量和系统的可扩展性。
(4)提升错误处理与输入验证能力
- 健壮的异常处理:学习如何捕获和处理各种异常,确保系统在面对意外输入或错误状态时依然稳定运行。
- 输入验证技术:掌握更复杂的输入验证方法,确保系统接收到的数据格式正确且合理,避免潜在的漏洞和错误。