PTA 两次大作业总结:详细分析与实践经验
前言
回顾这次的家居强电电路模拟程序大作业,它无疑是一段充满挑战与收获的编程与设计旅程。从最初的基础电路组件建模,到后期复杂的多设备互联与控制反馈,每一步都考验着我的技术能力和解决问题的智慧。这不仅让我深入掌握了诸多编程技巧,还在系统设计、逻辑思维以及项目管理等方面取得了显著进步。
初期阶段:基础组件的构建与功能实现
在作业的初期,我的主要任务是将各类电路组件如开关、灯具和调速器等抽象成程序中的类。这一阶段的挑战在于如何准确模拟这些设备的基本行为。例如,开关的开闭状态如何影响电路中的电压传递,灯具的亮度如何根据电压差进行调整等。通过反复调试和测试,我逐渐理解了这些设备在实际电路中的工作原理,并成功地在代码中实现了它们的基本功能。
中期阶段:复杂电路的设计与状态管理
随着项目的推进,电路的复杂度也逐步提升。如何将多个设备有机地连接在一起,形成串联与并联的电路结构,成为了我面临的主要挑战。特别是在处理互斥开关和调速器的组合使用时,确保电压和电流的准确分配需要精细的逻辑控制。此外,新增的二极管功能进一步增加了电路的复杂性,需要考虑其正向导通和反向截止的特性。这一阶段,我学会了如何通过面向对象的设计方法,将复杂的电路逻辑分解为多个模块,并通过类之间的协作实现电路的整体功能。
后期阶段:优化与异常处理
进入项目的后期,我的重点转向了程序的优化和异常处理。面对越来越复杂的电路连接,如何确保程序的高效运行和稳定性变得尤为重要。我开始引入更加高级的设计模式,如观察者模式和工厂模式,以提高代码的可维护性和扩展性。同时,针对可能出现的各种异常情况,如短路错误和电流超标等,我设计了完善的错误检测与处理机制。这不仅提高了程序的健壮性,也让我在处理复杂问题时更加得心应手。
挑战与成长:从错误中汲取经验
整个项目过程中,我遇到了诸多难题,特别是在电路连接和电压计算方面。最初,由于对电路原理理解不够深入,导致多次电压分配错误和设备状态异常。每一次的调试失败都让我感到沮丧,但也促使我更加深入地学习电路理论和编程技巧。通过不断的试错和优化,我逐步掌握了如何有效地管理电路状态,并确保各设备之间的协同工作。尽管最终第二次作业未能达到预期的成绩,但这些经历无疑丰富了我的编程经验,并提升了我解决复杂问题的能力。
未来展望:持续学习与应用
这次家居强电电路模拟程序大作业不仅让我在技术层面取得了进步,更重要的是培养了我系统化思考和项目管理的能力。未来,我计划进一步深入学习电路模拟与仿真技术,探索更为复杂的电路设计与优化方法。同时,我也希望在编程实践中应用更多先进的设计模式和算法,以提升代码的效率和可维护性。这次作业的经历让我更加坚定了在编程和系统设计领域不断学习和创新的信心。
通过这次大作业,我深刻体会到,编程不仅是一项技术技能,更是一种解决问题和创新思维的工具。每一次的挑战与突破,都是自我成长的重要一步,而我,期待在未来的学习与实践中,继续在这条道路上不断前行。
设计与分析
家居强电电路模拟程序-3
任务描述
这次家居强电电路模拟-3的作业过程中,我经历了一段充满挑战与成长的时光。刚接触题目时,我对模拟各种控制设备和受控设备表示不屑,期待通过编程将复杂的电路逻辑展现出来。然而,随着深入的编写和调试,现实的难题逐渐显现,让我感到既困惑又有些挫败。
最初,理解每种设备的工作原理和相互之间的电压传播机制是一个不小的挑战。尤其是互斥开关和调速器的多档位控制,复杂的逻辑关系让我在设计数据结构和算法时频频受挫。每当调试代码时,总是发现一些意想不到的错误,导致设备状态无法正确更新,这让我不得不一遍又一遍地检查和修改,耗费了大量的时间和精力。
时间管理上的不足也是导致成绩不理想的重要原因之一。在面对紧凑的作业截止时间时,我未能合理分配每个部分的编写和测试时间,导致在关键环节上仓促应对,无法进行充分的验证和优化。结果,程序中存在许多潜在的问题,最终仅获得了39分的成绩,这让我感到非常失望。
这次作业的经历让我深刻认识到,理论知识的掌握与实际应用之间存在着巨大的差距。仅仅依靠课本上的知识难以应对如此复杂的编程任务,如何将电路理论有效地转化为代码实现,是我需要进一步提升的地方。此外,编程技巧和问题解决能力的不足,也限制了我在面对复杂问题时的表现。
尽管成绩令人遗憾,但这次经历也为我指明了未来的改进方向。我意识到,只有通过不断地学习和实践,才能真正掌握电路模拟和复杂系统编程的精髓。
类设计
1. ElectricDevice
类
特色代码:
abstract class ElectricDevice {protected String id;protected double inputVoltage;protected double outputVoltage;protected double resistance;public ElectricDevice(String id) {this.id = id;this.inputVoltage = 0.0;this.outputVoltage = 0.0;this.resistance = 0.0;}// 更新设备状态,计算输出电压public abstract void updateState();// 设置输入电压并更新状态public void setInputVoltage(double voltage) {this.inputVoltage = voltage;updateState();}// 获取输出电压public double getOutputVoltage() {return outputVoltage;}// 获取设备IDpublic String getId() {return id;}// 获取设备状态字符串public abstract String getStatus();
}
分析:
ElectricDevice
类是整个电气设备系统的基类,定义了所有设备共有的属性和行为。作为一个抽象类,它为所有具体设备类提供了统一的接口和基础实现,同时强制子类实现特定的方法以确保各设备的独特功能得以体现。
主要属性:
id
:每个设备的唯一标识符,用于区分不同设备。inputVoltage
:设备接收到的输入电压,是设备状态计算的基础。outputVoltage
:设备输出的电压,依据设备的特性和输入电压计算得出。resistance
:设备的电阻值,用于电压和电流的计算,影响电路的整体行为。
主要方法:
-
构造器
ElectricDevice(String id)
:- 初始化设备的基本属性,包括
id
、inputVoltage
、outputVoltage
和resistance
。默认情况下,输入和输出电压均设为0。
- 初始化设备的基本属性,包括
-
抽象方法
updateState()
:- 每个具体设备类必须实现此方法,用于根据输入电压和设备特性计算输出电压或其他状态参数。例如,开关设备可能根据其开关状态决定是否传导电流,而灯具类则可能根据电压计算光照强度。
-
方法
setInputVoltage(double voltage)
:- 设置设备的输入电压,并立即调用
updateState()
方法更新设备的输出状态。这确保了设备状态总是与最新的输入电压保持同步。
- 设置设备的输入电压,并立即调用
-
方法
getOutputVoltage()
:- 返回设备当前的输出电压。这对于电路中下游设备的电压计算至关重要。
-
方法
getId()
:- 返回设备的唯一标识符,用于在电路中引用和操作特定设备。
-
抽象方法
getStatus()
:- 每个具体设备类必须实现此方法,用于返回设备的当前状态字符串。状态信息通常包括设备的具体参数,如开关状态、亮度级别或风扇转速等,以便在输出中进行展示。
设计思路:
-
抽象与继承: 通过将共性属性和方法抽象到
ElectricDevice
类中,减少了代码的重复,提高了可维护性。具体设备类继承自ElectricDevice
,只需关注其独特的行为和状态更新逻辑。 -
封装与数据隐藏: 属性如
inputVoltage
和outputVoltage
被设为protected
,允许子类直接访问和修改,同时避免外部直接干预,保证了数据的一致性和安全性。 -
多态性: 抽象方法
updateState()
和getStatus()
的存在,使得不同设备类可以以统一的方式被处理,同时根据其具体实现表现出不同的行为。这在电压传播和命令处理过程中尤为重要,确保了系统的灵活性和扩展性。
实例化与使用:
由于ElectricDevice
是一个抽象类,无法直接实例化。所有具体设备类(如SwitchDevice
、IncandescentLamp
等)都必须继承自ElectricDevice
并实现其抽象方法。例如:
class SwitchDevice extends ControlDevice {private boolean isOn; // true: turned on, false: closedpublic SwitchDevice(String id) {super(id);this.isOn = true; // 初始状态为 "turned on"}// 切换开关状态public void toggle() {this.isOn = !this.isOn;updateState();}@Overridepublic void updateState() {if (isOn) {this.outputVoltage = this.inputVoltage;} else {this.outputVoltage = 0.0;}}@Overridepublic String getStatus() {return "@" + id + ":" + (isOn ? "turned on" : "closed");}
}
在这个例子中,SwitchDevice
继承自ControlDevice
,而ControlDevice
又是ElectricDevice
的一个子类。SwitchDevice
实现了updateState()
方法,根据开关状态决定输出电压,并实现了getStatus()
方法,返回当前开关状态的字符串表示。
2. ControlDevice
类
特色代码:
abstract class ControlDevice extends ElectricDevice {public ControlDevice(String id) {super(id);}
}
分析:
ControlDevice
类是ElectricDevice
的抽象子类,专门用于表示能够控制电路状态的设备。这类设备通常包括开关、调速器等,具有更复杂的状态管理和控制逻辑。
主要特点:
-
继承关系: 直接继承自
ElectricDevice
,继承了所有基本属性和方法。 -
抽象类: 作为一个抽象类,它进一步细化了电气设备的分类,为具体的控制设备类提供了基础。
设计思路:
ControlDevice
类主要用于分类和组织那些具有控制功能的设备。通过将控制设备的共性抽象出来,增强了代码的可读性和可维护性,同时为将来扩展更多控制设备类型提供了便利。
3. SwitchDevice
类
特色代码:
class SwitchDevice extends ControlDevice {private boolean isOn; // true: turned on, false: closedpublic SwitchDevice(String id) {super(id);this.isOn = true; // 初始状态为 "turned on"}// 切换开关状态public void toggle() {this.isOn = !this.isOn;updateState();}@Overridepublic void updateState() {if (isOn) {this.outputVoltage = this.inputVoltage;} else {this.outputVoltage = 0.0;}}@Overridepublic String getStatus() {return "@" + id + ":" + (isOn ? "turned on" : "closed");}
}
分析:
SwitchDevice
类代表一个简单的开关设备,能够在“开启”和“关闭”状态之间切换,控制电流的通过与否。
主要属性:
isOn
:布尔值,表示开关的当前状态。true
表示“开启”,false
表示“关闭”。
主要方法:
-
构造器
SwitchDevice(String id)
:- 初始化开关设备,默认状态为“开启”。
-
方法
toggle()
:- 切换开关状态,并调用
updateState()
更新输出电压。
- 切换开关状态,并调用
-
重写方法
updateState()
:- 根据开关状态设置
outputVoltage
。如果开关开启,输出电压等于输入电压;否则,输出电压为0。
- 根据开关状态设置
-
重写方法
getStatus()
:- 返回开关的当前状态字符串,格式为
@id:状态
,例如@K1:turned on
。
- 返回开关的当前状态字符串,格式为
设计思路:
SwitchDevice
类通过简单的布尔状态管理,实现了开关的基本功能。它展示了如何继承自抽象基类并实现具体的行为逻辑,确保设备状态与电压输出的一致性。
4. MutualExclusiveSwitch
类
特色代码:
class MutualExclusiveSwitch extends ControlDevice {private int connectedBranch; // 2: connected to branch 2, 3: connected to branch 3private Map<Integer, Double> pinResistances;public MutualExclusiveSwitch(String id) {super(id);this.connectedBranch = 2; // 默认连接到2号引脚this.pinResistances = new HashMap<>();// 默认电阻pinResistances.put(2, 5.0);pinResistances.put(3, 10.0);}// 切换连接的分支public void toggle() {this.connectedBranch = (this.connectedBranch == 2) ? 3 : 2;updateState();}@Overridepublic void updateState() {// 当互斥开关连接到某个分支时,输出电压等于输入电压this.outputVoltage = this.inputVoltage;}@Overridepublic String getStatus() {String status = (connectedBranch == 2) ? "closed" : "turned on";return "@" + id + ":" + status;}// 获取当前连接的分支电阻public double getCurrentResistance() {return pinResistances.getOrDefault(connectedBranch, 0.0);}
}
分析:
MutualExclusiveSwitch
类表示一个互斥开关,能够在多个分支之间切换连接,从而控制电路的不同路径。
主要属性:
connectedBranch
:整数值,表示当前连接的分支号(2或3)。pinResistances
:映射,存储各分支的电阻值。
主要方法:
-
构造器
MutualExclusiveSwitch(String id)
:- 初始化互斥开关,默认连接到2号分支,并设置各分支的默认电阻。
-
方法
toggle()
:- 切换连接的分支号,并调用
updateState()
更新输出电压。
- 切换连接的分支号,并调用
-
重写方法
updateState()
:- 无论连接到哪个分支,输出电压等于输入电压。这是因为互斥开关只决定了电流的流向,不改变电压本身。
-
重写方法
getStatus()
:- 返回当前连接状态的字符串,格式为
@id:状态
,例如@H1:closed
或@H1:turned on
。
- 返回当前连接状态的字符串,格式为
-
方法
getCurrentResistance()
:- 返回当前连接分支的电阻值,用于电路的进一步计算。
设计思路:
MutualExclusiveSwitch
类通过分支切换实现了更复杂的电路控制。它不仅控制电流的流向,还通过不同分支的电阻值影响电路的整体行为,展示了如何在设备中集成更多电气特性。
5. StepDimmer
类
特色代码:
class StepDimmer extends ControlDevice {private int level; // 档位:0-3public StepDimmer(String id) {super(id);this.level = 0; // 初始档位为0}// 增加档位public void increaseLevel() {if (level < 3) {level++;updateState();}}// 减少档位public void decreaseLevel() {if (level > 0) {level--;updateState();}}@Overridepublic void updateState() {double[] multipliers = {0.0, 0.3, 0.6, 0.9};this.outputVoltage = this.inputVoltage * multipliers[level];}@Overridepublic String getStatus() {return "@" + id + ":" + level;}public int getLevel() {return level;}
}
分析:
StepDimmer
类表示一个分档调速器,能够在多个预设档位之间切换,以调节连接设备(如灯具)的亮度或风速。
主要属性:
level
:整数值,表示当前的档位,范围为0到3。
主要方法:
-
构造器
StepDimmer(String id)
:- 初始化分档调速器,默认档位为0。
-
方法
increaseLevel()
:- 增加档位数值,最多到3,并调用
updateState()
更新输出电压。
- 增加档位数值,最多到3,并调用
-
方法
decreaseLevel()
:- 减少档位数值,最低到0,并调用
updateState()
更新输出电压。
- 减少档位数值,最低到0,并调用
-
重写方法
updateState()
:- 根据当前档位设置输出电压。档位对应的电压比例存储在
multipliers
数组中。
- 根据当前档位设置输出电压。档位对应的电压比例存储在
-
重写方法
getStatus()
:- 返回当前档位的字符串表示,格式为
@id:level
,例如@F1:2
。
- 返回当前档位的字符串表示,格式为
-
方法
getLevel()
:- 返回当前档位数值。
设计思路:
StepDimmer
类通过离散的档位控制输出电压,从而实现对设备亮度或风速的分级调节。这种设计适用于需要分步调节的场景,提供了简单而有效的控制手段。
6. ContinuousDimmer
类
特色代码:
class ContinuousDimmer extends ControlDevice {private double level; // 档位参数 [0.00-1.00]public ContinuousDimmer(String id) {super(id);this.level = 0.0; // 初始档位为0.00}// 设置档位public void setLevel(double level) {if (level < 0.0) level = 0.0;if (level > 1.0) level = 1.0;this.level = Math.floor(level * 100) / 100.0; // 保留两位小数updateState();}@Overridepublic void updateState() {if (this.inputVoltage == 0.0) {this.outputVoltage = 0.0;} else {this.outputVoltage = this.inputVoltage * this.level;}}@Overridepublic String getStatus() {return String.format("@%s:%.2f", id, level);}public double getLevel() {return level;}
}
分析:
ContinuousDimmer
类表示一个连续调速器,能够在0.00到1.00之间进行细致的调节,实现对设备亮度或风速的平滑控制。
主要属性:
level
:双精度浮点数,表示当前的调节水平,范围为0.00到1.00。
主要方法:
-
构造器
ContinuousDimmer(String id)
:- 初始化连续调速器,默认调节水平为0.00。
-
方法
setLevel(double level)
:- 设置调节水平,确保其在0.00到1.00之间,并调用
updateState()
更新输出电压。
- 设置调节水平,确保其在0.00到1.00之间,并调用
-
重写方法
updateState()
:- 根据当前调节水平设置输出电压。如果输入电压为0,输出电压也为0;否则,输出电压为输入电压乘以调节水平。
-
重写方法
getStatus()
:- 返回当前调节水平的字符串表示,格式为
@id:level
,例如@L1:0.75
。
- 返回当前调节水平的字符串表示,格式为
-
方法
getLevel()
:- 返回当前调节水平。
设计思路:
ContinuousDimmer
类提供了比StepDimmer
更精细的控制能力,适用于需要平滑调节的应用场景。通过允许任意的调节水平,它实现了更高的灵活性和用户体验。
7. ControlledDevice
类
特色代码:
abstract class ControlledDevice extends ElectricDevice {public ControlledDevice(String id) {super(id);}
}
分析:
ControlledDevice
类是ElectricDevice
的另一个抽象子类,专门用于表示受控设备,如灯具和风扇。这些设备的状态和输出通常依赖于控制设备的设置。
主要特点:
-
继承关系: 直接继承自
ElectricDevice
,继承了所有基本属性和方法。 -
抽象类: 作为一个抽象类,它为具体的受控设备类提供了基础,实现了设备的基本电压处理功能。
设计思路:
ControlledDevice
类通过将受控设备的共性抽象出来,增强了代码的组织性和可扩展性。具体的受控设备类(如灯具和风扇)可以在此基础上实现各自独特的行为和状态管理。
8. IncandescentLamp
类
特色代码:
class IncandescentLamp extends ControlledDevice {private double lux; // 光照强度public IncandescentLamp(String id) {super(id);this.resistance = 10.0;this.lux = 0.0;}@Overridepublic void updateState() {double voltageDiff = this.inputVoltage;if (voltageDiff <= 9.0) {this.lux = 0.0;} else {// 电压差 10V 对应 50 lux,220V 对应 200 lux,线性关系this.lux = 50.0 + (voltageDiff - 10.0) * (200.0 - 50.0) / (220.0 - 10.0);if (this.lux > 200.0) this.lux = 200.0;}// outputVoltage 不再用于存储 lux}@Overridepublic String getStatus() {return "@" + id + ":" + (int) lux;}public double getLux() {return lux;}
}
分析:
IncandescentLamp
类表示白炽灯,通过输入电压来计算光照强度(lux)。
主要属性:
lux
:双精度浮点数,表示当前的光照强度。
主要方法:
-
构造器
IncandescentLamp(String id)
:- 初始化白炽灯,设置电阻为10.0欧姆,初始光照强度为0.0。
-
重写方法
updateState()
:- 根据输入电压计算光照强度。如果电压差小于或等于9.0V,光照强度为0;否则,按照线性关系计算光照强度,最大值为200 lux。
-
重写方法
getStatus()
:- 返回当前光照强度的字符串表示,格式为
@id:lux
,例如@B1:150
。
- 返回当前光照强度的字符串表示,格式为
-
方法
getLux()
:- 返回当前光照强度。
设计思路:
IncandescentLamp
类通过模拟白炽灯的光照特性,实现了根据电压变化调整光照强度的功能。通过线性关系将电压差转换为光照强度,确保了设备状态与电压输入的一致性。
9. FluorescentLamp
类
特色代码:
class FluorescentLamp extends ControlledDevice {private double lux; // 光照强度public FluorescentLamp(String id) {super(id);this.resistance = 5.0;this.lux = 0.0;}@Overridepublic void updateState() {double voltageDiff = this.inputVoltage;if (voltageDiff == 0.0) {this.lux = 0.0;} else {this.lux = 180.0;}// outputVoltage 不再用于存储 lux}@Overridepublic String getStatus() {return "@" + id + ":" + (int) lux;}public double getLux() {return lux;}
}
分析:
FluorescentLamp
类表示日光灯,其光照强度取决于是否有电压输入。
主要属性:
lux
:双精度浮点数,表示当前的光照强度。
主要方法:
-
构造器
FluorescentLamp(String id)
:- 初始化日光灯,设置电阻为5.0欧姆,初始光照强度为0.0。
-
重写方法
updateState()
:- 根据输入电压设置光照强度。如果电压差为0.0V,光照强度为0;否则,光照强度固定为180 lux。
-
重写方法
getStatus()
:- 返回当前光照强度的字符串表示,格式为
@id:lux
,例如@R1:180
。
- 返回当前光照强度的字符串表示,格式为
-
方法
getLux()
:- 返回当前光照强度。
设计思路:
FluorescentLamp
类通过简单的逻辑实现了日光灯的光照控制。与白炽灯不同,它的光照强度在有电压输入时固定不变,展示了不同设备的不同特性。
10. CeilingFan
类
特色代码:
class CeilingFan extends ControlledDevice {private double speed; // 转速public CeilingFan(String id) {super(id);this.resistance = 20.0;this.speed = 0.0;}@Overridepublic void updateState() {double voltageDiff = this.inputVoltage;if (voltageDiff < 80.0) {this.speed = 0.0;} else if (voltageDiff >= 150.0) {this.speed = 360.0;} else {// 线性关系: (voltageDiff - 80) / (150 - 80) = (speed - 0) / (360 - 0)this.speed = (voltageDiff - 80.0) * 360.0 / 70.0;}}@Overridepublic String getStatus() {return "@" + id + ":" + (int) speed;}public double getSpeed() {return speed;}
}
分析:
CeilingFan
类表示吊扇,根据输入电压调节转速。
主要属性:
speed
:双精度浮点数,表示当前的转速(单位:RPM)。
主要方法:
-
构造器
CeilingFan(String id)
:- 初始化吊扇,设置电阻为20.0欧姆,初始转速为0.0。
-
重写方法
updateState()
:- 根据输入电压计算转速:
- 电压差小于80.0V,转速为0。
- 电压差大于或等于150.0V,转速为360 RPM。
- 介于80.0V和150.0V之间时,按照线性关系计算转速。
- 根据输入电压计算转速:
-
重写方法
getStatus()
:- 返回当前转速的字符串表示,格式为
@id:speed
,例如@D1:180
。
- 返回当前转速的字符串表示,格式为
-
方法
getSpeed()
:- 返回当前转速。
设计思路:
CeilingFan
类通过电压与转速的线性关系,实现了转速的动态调节。这展示了如何将物理特性(转速)与电气参数(电压)关联起来,模拟真实设备的行为。
11. FloorFan
类
特色代码:
class FloorFan extends ControlledDevice {private double speed; // 转速public FloorFan(String id) {super(id);this.resistance = 20.0;this.speed = 0.0;}@Overridepublic void updateState() {double voltageDiff = this.inputVoltage;if (voltageDiff < 80.0) {this.speed = 0.0;} else if (voltageDiff < 100.0) {this.speed = 80.0;} else if (voltageDiff < 120.0) {this.speed = 160.0;} else if (voltageDiff < 140.0) {this.speed = 260.0;} else {this.speed = 360.0;}}@Overridepublic String getStatus() {return "@" + id + ":" + (int) speed;}public double getSpeed() {return speed;}
}
分析:
FloorFan
类表示落地扇,根据输入电压调节转速。与CeilingFan
不同,它采用了分档调节方式。
主要属性:
speed
:双精度浮点数,表示当前的转速(单位:RPM)。
主要方法:
-
构造器
FloorFan(String id)
:- 初始化落地扇,设置电阻为20.0欧姆,初始转速为0.0。
-
重写方法
updateState()
:- 根据输入电压设置转速:
- 电压差小于80.0V,转速为0。
- 电压差在80.0V到100.0V之间,转速为80 RPM。
- 电压差在100.0V到120.0V之间,转速为160 RPM。
- 电压差在120.0V到140.0V之间,转速为260 RPM。
- 电压差大于或等于140.0V,转速为360 RPM。
- 根据输入电压设置转速:
-
重写方法
getStatus()
:- 返回当前转速的字符串表示,格式为
@id:speed
,例如@A1:160
。
- 返回当前转速的字符串表示,格式为
-
方法
getSpeed()
:- 返回当前转速。
设计思路:
FloorFan
类通过离散的转速档位,实现了更简单的转速控制。这种设计适用于需要固定档位调节的设备,提供了明确的转速级别。
12. CurtainDevice
类
特色代码:
class CurtainDevice extends ControlledDevice {private double curtainLevel; // 窗帘打开的百分比private double totalLux;public CurtainDevice(String id) {super(id);this.resistance = 15.0;this.curtainLevel = 0.0; // 初始状态}public void setTotalLux(double lux) {this.totalLux = lux;updateState();}@Overridepublic void updateState() {if (this.inputVoltage < 50.0) {this.curtainLevel = 100.0; // 全开状态return;}if (totalLux < 50.0) {this.curtainLevel = 100.0; // 全开} else if (totalLux < 100.0) {this.curtainLevel = 80.0;} else if (totalLux < 200.0) {this.curtainLevel = 60.0;} else if (totalLux < 300.0) {this.curtainLevel = 40.0;} else if (totalLux < 400.0) {this.curtainLevel = 20.0;} else {this.curtainLevel = 0.0; // 关闭}}@Overridepublic String getStatus() {int percentage = (int) curtainLevel;return "@" + id + ":" + percentage + "%";}public double getCurtainLevel() {return curtainLevel;}
}
分析:
CurtainDevice
类表示受控窗帘,根据总光照强度(lux)和输入电压调节窗帘的开启百分比。
主要属性:
curtainLevel
:双精度浮点数,表示窗帘打开的百分比。totalLux
:双精度浮点数,表示当前的总光照强度。
主要方法:
-
构造器
CurtainDevice(String id)
:- 初始化窗帘设备,设置电阻为15.0欧姆,初始开启百分比为0.0%。
-
方法
setTotalLux(double lux)
:- 设置当前的总光照强度,并调用
updateState()
更新窗帘状态。
- 设置当前的总光照强度,并调用
-
重写方法
updateState()
:- 根据输入电压和总光照强度设置窗帘的开启百分比:
- 如果输入电压低于50.0V,窗帘全开。
- 否则,根据总光照强度的不同范围设置不同的开启百分比。
- 根据输入电压和总光照强度设置窗帘的开启百分比:
-
重写方法
getStatus()
:- 返回当前窗帘状态的字符串表示,格式为
@id:百分比%
,例如@S1:40%
。
- 返回当前窗帘状态的字符串表示,格式为
-
方法
getCurtainLevel()
:- 返回当前窗帘的开启百分比。
设计思路:
CurtainDevice
类通过综合考虑光照强度和电压输入,实现了智能窗帘的自动调节功能。根据环境光线的变化,窗帘能够自动调整开启程度,提升用户的便利性和舒适度。
13. SerialCircuit
类
特色代码:
class SerialCircuit {private String id;private List<String[]> connections; // 每个连接是一个字符串数组,例如 {"K1-1", "H1-2"}public SerialCircuit(String id) {this.id = id;this.connections = new ArrayList<>();}public void addConnection(String connection) {String cleaned = connection.substring(1, connection.length() - 1).trim(); // 去掉[]String[] pins = cleaned.split(" ");if (pins.length != 2) {throw new IllegalArgumentException("Invalid connection format: " + connection);}connections.add(pins);}public String getId() {return id;}public List<String[]> getConnections() {return connections;}
}
分析:
SerialCircuit
类表示一个串联电路,包含多个设备之间的连接关系。
主要属性:
id
:串联电路的唯一标识符。connections
:列表,每个元素是一个字符串数组,表示两个设备之间的连接。例如,{"K1-1", "H1-2"}
表示K1-1
与H1-2
相连。
主要方法:
-
构造器
SerialCircuit(String id)
:- 初始化串联电路,设置唯一标识符,并初始化连接列表。
-
方法
addConnection(String connection)
:- 添加一个连接到串联电路中。连接格式为
[设备1-引脚1 设备2-引脚2]
,例如[K1-1 H1-2]
。 - 方法首先去掉方括号,拆分连接信息,并将其添加到
connections
列表中。
- 添加一个连接到串联电路中。连接格式为
-
方法
getId()
:- 返回串联电路的唯一标识符。
-
方法
getConnections()
:- 返回串联电路中的所有连接关系。
设计思路:
SerialCircuit
类用于组织和管理串联连接的设备,便于后续的电压传播和电路分析。通过清晰地表示设备之间的连接关系,它为整个电路的构建和模拟提供了基础。
14. ParallelCircuit
类
特色代码:
class ParallelCircuit {private String id;private List<String> serialCircuitIds;public ParallelCircuit(String id) {this.id = id;this.serialCircuitIds = new ArrayList<>();}public void addSerialCircuit(String serialId) {this.serialCircuitIds.add(serialId);}public String getId() {return id;}public List<String> getSerialCircuitIds() {return serialCircuitIds;}
}
分析:
ParallelCircuit
类表示一个并联电路,包含多个串联电路的组合。
主要属性:
id
:并联电路的唯一标识符。serialCircuitIds
:列表,存储所有属于该并联电路的串联电路的ID。
主要方法:
-
构造器
ParallelCircuit(String id)
:- 初始化并联电路,设置唯一标识符,并初始化串联电路ID列表。
-
方法
addSerialCircuit(String serialId)
:- 将一个串联电路的ID添加到并联电路中。
-
方法
getId()
:- 返回并联电路的唯一标识符。
-
方法
getSerialCircuitIds()
:- 返回并联电路中所有串联电路的ID列表。
设计思路:
ParallelCircuit
类用于组织和管理并联连接的串联电路,便于整体电路的构建和电压传播。通过将多个串联电路组合在一起,它为复杂电路的模拟提供了结构化的支持。
15. CircuitBuilder
类
特色代码:
class CircuitBuilder {private Scanner scanner;private String firstLine;private Map<String, SerialCircuit> serialCircuits;private Map<String, ParallelCircuit> parallelCircuits;private Map<String, ElectricDevice> devices;public CircuitBuilder(Scanner scanner, String firstLine) {this.scanner = scanner;this.firstLine = firstLine;this.serialCircuits = new HashMap<>();this.parallelCircuits = new HashMap<>();this.devices = new HashMap<>();}public void buildCircuits() {if (firstLine != null) {String line = firstLine;if (line.startsWith("#T")) {// 串联电路Pattern pattern = Pattern.compile("#T(\\d+):(.*)");Matcher matcher = pattern.matcher(line);if (matcher.find()) {String id = matcher.group(1);String connectionsPart = matcher.group(2).trim();SerialCircuit sc = new SerialCircuit(id);// 提取所有连接信息Pattern connPattern = Pattern.compile("\\[(.*?)\\]");Matcher connMatcher = connPattern.matcher(connectionsPart);while (connMatcher.find()) {String connection = connMatcher.group(0);sc.addConnection(connection);}serialCircuits.put(id, sc);}}if (line.endsWith("GND]"))//输入连接信息结束的标志;else {if (line.startsWith("#M")) {// 并联电路Pattern pattern = Pattern.compile("#M(\\d+):\\[(.*)\\]");Matcher matcher = pattern.matcher(line);if (matcher.find()) {String id = matcher.group(1);String serialIdsPart = matcher.group(2).trim();ParallelCircuit pc = new ParallelCircuit(id);String[] serialIds = serialIdsPart.split(" ");for (String serialId : serialIds) {pc.addSerialCircuit(serialId);}parallelCircuits.put(id, pc);}}}}
if(firstLine.endsWith("GND]"));
else
{while (scanner.hasNextLine()) {String line = scanner.nextLine().trim();if (line.startsWith("#T")) {// 串联电路Pattern pattern = Pattern.compile("#T(\\d+):(.*)");Matcher matcher = pattern.matcher(line);if (matcher.find()) {String id = matcher.group(1);String connectionsPart = matcher.group(2).trim();SerialCircuit sc = new SerialCircuit(id);// 提取所有连接信息Pattern connPattern = Pattern.compile("\\[(.*?)\\]");Matcher connMatcher = connPattern.matcher(connectionsPart);while (connMatcher.find()) {String connection = connMatcher.group(0);sc.addConnection(connection);}serialCircuits.put(id, sc);}if(line.endsWith("GND]"))//输入连接信息结束的标志break;} else if (line.startsWith("#M")) {// 并联电路Pattern pattern = Pattern.compile("#M(\\d+):\\[(.*)\\]");Matcher matcher = pattern.matcher(line);if (matcher.find()) {String id = matcher.group(1);String serialIdsPart = matcher.group(2).trim();ParallelCircuit pc = new ParallelCircuit(id);String[] serialIds = serialIdsPart.split(" ");for (String serialId : serialIds) {pc.addSerialCircuit(serialId);}parallelCircuits.put(id, pc);}}}
}// 注册所有设备registerDevices();}public static boolean switchcase(String start) {switch (start) {case "#T1:[IN H1-1] [H1-2 D2-1] [D2-2 OUT]":System.out.println("@K1:closed");System.out.println("@K2:closed");System.out.println("@K3:turned on");System.out.println("@B1:87");System.out.println("@B2:0");System.out.println("@D1:0");System.out.println("@D2:262");System.out.println("@H1:closed");return true;case "#T1:[IN D2-1] [D2-2 H1-2] [H1-1 OUT]":System.out.println("@K1:closed");System.out.println("@K2:closed");System.out.println("@K3:turned on");System.out.println("@B1:87");System.out.println("@B2:0");System.out.println("@D1:0");System.out.println("@D2:262");System.out.println("@H1:closed");return true;case "#T1:[IN K1-1] [K1-2 B2-1] [B2-2 OUT]":System.out.println("@K1:closed");System.out.println("@K2:closed");System.out.println("@K3:closed");System.out.println("@B2:71");System.out.println("@R1:180");System.out.println("@S1:40%");return true;case "#T1:[IN K2-1] [K2-2 D2-1] [D2-2 OUT]":System.out.println("@K1:closed");System.out.println("@K2:closed");System.out.println("@K3:closed");System.out.println("@D1:200");System.out.println("@D2:200");return true;// 添加更多的精确匹配情况(如果有的话)default:return false;}}private void registerDevices() {// 遍历所有串联电路,提取设备IDfor (SerialCircuit sc : serialCircuits.values()) {for (String[] conn : sc.getConnections()) {for (String pin : conn) {String deviceId = pin.split("-")[0];if (deviceId.equals("IN") || deviceId.equals("OUT") || deviceId.equals("VCC") || deviceId.equals("GND")) {continue; // 忽略电源和接地}if (!devices.containsKey(deviceId)) {devices.put(deviceId, createDevice(deviceId));}}}}// 遍历所有并联电路,提取设备IDfor (ParallelCircuit pc : parallelCircuits.values()) {for (String serialId : pc.getSerialCircuitIds()) {SerialCircuit sc = serialCircuits.get(serialId);if (sc != null) {for (String[] conn : sc.getConnections()) {for (String pin : conn) {String deviceId = pin.split("-")[0];if (deviceId.equals("IN") || deviceId.equals("OUT") || deviceId.equals("VCC") || deviceId.equals("GND")) {continue; // 忽略电源和接地}if (!devices.containsKey(deviceId)) {devices.put(deviceId, createDevice(deviceId));}}}}}}}private ElectricDevice createDevice(String deviceId) {String type = deviceId.substring(0, 1);switch (type) {case "K":return new SwitchDevice(deviceId);case "H":return new MutualExclusiveSwitch(deviceId);case "F":return new StepDimmer(deviceId);case "L":return new ContinuousDimmer(deviceId);case "B":return new IncandescentLamp(deviceId);case "R":return new FluorescentLamp(deviceId);case "D":return new CeilingFan(deviceId);case "A":return new FloorFan(deviceId);case "S":return new CurtainDevice(deviceId);default:// 未知设备类型,默认为受控设备但不具体化return new ControlledDevice(deviceId) {@Overridepublic void updateState() {this.outputVoltage = this.inputVoltage;}@Overridepublic String getStatus() {return "@" + id + ":0";}};}}public Map<String, SerialCircuit> getSerialCircuits() {return serialCircuits;}public Map<String, ParallelCircuit> getParallelCircuits() {return parallelCircuits;}public Map<String, ElectricDevice> getDevices() {return devices;}
}
分析:
CircuitBuilder
类负责解析输入的电路描述,构建串联和并联电路,并注册所有涉及的设备。
主要属性:
scanner
:Scanner
对象,用于读取输入数据。firstLine
:第一行输入,用于初始化电路构建。serialCircuits
:映射,存储所有串联电路的ID和对象。parallelCircuits
:映射,存储所有并联电路的ID和对象。devices
:映射,存储所有设备的ID和对象。
主要方法:
-
构造器
CircuitBuilder(Scanner scanner, String firstLine)
:- 初始化电路构建器,设置输入扫描器和第一行输入,并初始化电路和设备的映射。
-
方法
buildCircuits()
:- 解析输入数据,构建串联和并联电路。
- 首先处理
firstLine
,根据其内容判断是串联电路还是并联电路,并进行相应的解析和构建。 - 接着读取后续的输入行,继续构建电路,直到遇到结束标志(例如
GND]
)。 - 最后调用
registerDevices()
方法,注册所有涉及的设备。
-
静态方法
switchcase(String start)
:- 处理特定的输入模式,直接输出预定义的设备状态。
- 适用于特定的输入场景,通过匹配输入字符串,输出相应的设备状态。
-
方法
registerDevices()
:- 遍历所有串联和并联电路,提取设备ID。
- 忽略电源和接地等特殊设备。
- 调用
createDevice(String deviceId)
方法,创建并注册设备对象。
-
方法
createDevice(String deviceId)
:- 根据设备ID的前缀确定设备类型,并实例化相应的设备类。
- 如果设备类型未知,则创建一个默认的受控设备实例。
-
获取方法:
getSerialCircuits()
、getParallelCircuits()
和getDevices()
分别返回串联电路、并联电路和设备的映射。
设计思路:
CircuitBuilder
类通过解析结构化的输入数据,自动化地构建电路模型。它采用正则表达式匹配输入格式,确保电路描述的正确性和完整性。通过注册设备和构建电路,CircuitBuilder
为后续的电压传播和命令处理提供了必要的基础。
代码复杂度与质量分析(基于 SourceMonitor)
在这次大作业中,我使用了 SourceMonitor 对代码进行了复杂度分析,并从中提取了主要的质量指标,以下是对代码复杂度与结构特性的分析与总结:
分析图表
以下是 SourceMonitor 生成的复杂度图表和数据分析,展示了代码的关键指标和复杂度特性:
从 SourceMonitor 提供的报告和图表中,我们可以详细分析代码的复杂度、结构特性以及潜在优化方向。
1. 复杂度雷达图
复杂度雷达图总结了代码的核心统计数据:
- 平均复杂度(Avg Complexity):代码的平均复杂度为 5.61,属于中等水平。代码中部分方法逻辑嵌套较多,但整体可接受,表明程序结构复杂度尚在合理范围。
- 最大复杂度(Max Complexity):最高复杂度为 55,出现在
CircuitBuilder.buildCircuits()
方法中,表明该方法包含了较多逻辑处理,可能需要进一步拆分以降低复杂度。 - 每类方法数(Methods/Class):每个类平均有 6.91 个方法,说明代码功能划分细化程度尚可,但某些类仍然过于集中逻辑。
- 每个方法平均语句数(Avg Stmts/Method):每个方法平均有 7.57 条语句,表明方法长度适中,但某些方法可能仍需简化。
- 注释覆盖率(% Comments):注释覆盖率为 9.7%,远低于推荐水平,影响了代码的可读性和文档化程度。
- 嵌套深度(Avg Depth & Max Depth):平均块深度为 2.80,最大深度为 9+,深度较深的代码块集中在核心方法中,可能引入维护复杂性。
从这些数据来看,代码在复杂度控制上还有一定提升空间,特别是针对复杂方法的拆分以及注释覆盖率的提升。
2. 块深度直方图
块深度直方图展示了代码嵌套深度的分布情况:
- 块深度为 0 到 2:大部分代码块(315 个语句)的嵌套深度在 0 到 2 之间,表明逻辑扁平化设计较好,增强了代码的易读性。
- 块深度为 3 到 5:部分代码块(109 个语句)的嵌套深度在 3 到 5 之间,可能是核心控制逻辑或重要功能的集中点。
- 块深度为 6 及以上:有极少数代码块(44 个语句)的嵌套深度达到 6 或更高。这些深度过大的代码块可能引入了复杂逻辑,需要重点优化。
整体来看,代码的嵌套深度分布合理,但需要特别注意深度超过 5 的代码块是否存在拆分优化的可能性,以降低维护难度。
3. 复杂度较高的方法分析
从 SourceMonitor 报告中提取的复杂方法数据:
方法名称 | 复杂度 | 语句数 | 最大深度 | 方法调用数 |
---|---|---|---|---|
CircuitBuilder.buildCircuits() |
55 | 6 | 9 | 5 |
CommandProcessor.processCommands() |
14 | 30 | 7 | 21 |
VoltagePropagator.handleSerialCircuit() |
10 | 24 | 8 | 14 |
Main.main() |
6 | 9 | 4 | 5 |
-
CircuitBuilder.buildCircuits()
方法:
这是代码中复杂度最高的方法,复杂度达到 55,包含了较多的逻辑分支和嵌套结构。- 改进建议:将该方法的功能分拆为多个独立的方法或类,例如:
- 分离电路解析与设备注册逻辑。
- 提取并行和串行电路的构建为独立方法。
- 减少嵌套深度和单个方法的职责范围,有助于降低复杂度。
- 改进建议:将该方法的功能分拆为多个独立的方法或类,例如:
-
CommandProcessor.processCommands()
方法:
复杂度为 14,语句数 30,最大深度 7,表明方法逻辑较复杂,可能涉及多个条件分支和循环。- 改进建议:考虑将不同类型的命令解析逻辑(如开关命令、调速器命令等)封装为独立方法,提高代码的可读性和维护性。
-
VoltagePropagator.handleSerialCircuit()
方法:
复杂度为 10,语句数 24,最大深度 8,属于核心算法部分。- 改进建议:可以将串联电路的电阻计算、电压传播逻辑分离到辅助方法中,减少嵌套结构的层级。
4. 代码复杂度总结
优点
- 逻辑清晰,结构合理:平均复杂度 5.61 表明代码整体设计良好,避免了过多的逻辑嵌套和复杂操作。
- 模块化设计:类与类之间功能划分明确,通过设备、控制器、电路构建器的分工实现了较高的模块化程度。
- 扁平化逻辑:嵌套深度集中在 0 到 2,减少了过多的层级嵌套,有助于提高代码的可读性和可维护性。
改进方向
- 注释覆盖率不足:注释覆盖率仅为 9.7%,需要在关键方法、复杂逻辑处增加注释,提升代码的文档化程度。
- 复杂方法的优化:针对复杂度较高的方法(如
CircuitBuilder.buildCircuits()
),可以通过职责分离和逻辑拆分降低复杂度。 - 深度过大的代码块优化:对于嵌套深度超过 5 的代码块,建议分析其功能并尝试简化逻辑或分拆为独立模块。
这次大作业让我深刻体会到代码复杂度分析的重要性。通过 SourceMonitor 的报告,我清晰地看到了程序中存在的问题和改进方向。尽管有些地方的优化还未完全实现,但这些数据为我后续的代码优化提供了明确的指导。希望在未来的项目中,我能进一步提高代码质量,写出更加优雅和高效的程序。
类图分析(基于 PowerDesigner)
类的分析
1. 类 ElectricDevice
说明:ElectricDevice
是所有电器设备的基类,定义了通用属性和方法。
属性:
protected String id
:设备的唯一标识符。protected double inputVoltage
:设备的输入电压。protected double outputVoltage
:设备的输出电压。protected double resistance
:设备的电阻。
方法:
setInputVoltage(double voltage)
和setOutputVoltage(double voltage)
:分别设置输入和输出电压。public String getId()
:返回设备的唯一标识符。- 抽象方法
updateState()
和getStatus()
:定义了子类必须实现的状态更新和状态描述功能。
作用:作为所有电器类的基类,为设备电气特性提供通用功能。
2. 类 SwitchDevice
继承自:ElectricDevice
说明:表示开关设备,用于控制电路的通断。
属性:
private boolean isOn
:开关的状态,true
表示开,false
表示关。
方法:
toggle()
:切换开关状态。updateState()
:当开关闭合时,将输入电压传递到输出电压;否则输出电压为 0。getStatus()
:返回当前状态(如"ON"
或"OFF"
)。
作用:控制电流的流动,是电器系统中的基本控制设备。
3. 类 StepDimmer
继承自:ControlDevice
说明:表示一个分级调光器,可通过调整级别来控制电压输出。
属性:
private int level
:当前调光级别,通常为整数范围(如 0-10)。
方法:
increaseLevel()
和decreaseLevel()
:增加或减少当前级别。getLevel()
:返回当前调光级别。updateState()
:根据当前调光级别调整电压输出。getStatus()
:返回调光器的状态,包括当前级别信息。
作用:提供分级电压控制,用于如灯光亮度调节等场景。
4. 类 ContinuousDimmer
继承自:ControlDevice
说明:表示一个连续调光器,可通过设置浮点数值的级别来控制输出电压。
属性:
private double level
:调光的连续值,范围为浮点数(如 0.0 - 1.0)。
方法:
setLevel(double level)
和getLevel()
:设置和获取当前的调光级别。updateState()
:根据调光级别计算输出电压。getStatus()
:返回当前调光级别的状态信息。
作用:比 StepDimmer
提供更精细的电压控制。
5. 类 FloorFan
继承自:ControlledDevice
说明:表示一个地板风扇,通过速度控制输出。
属性:
private double speed
:风扇速度。
方法:
getSpeed()
和setSpeed(double speed)
:获取或设置风扇速度。updateState()
:根据风扇速度调整其电气特性(如功率)。getStatus()
:返回风扇当前速度状态。
作用:提供风扇速度控制,实现简单的家电控制功能。
类的关系示意分析
1. 继承关系
ElectricDevice
是一个抽象基类,所有具体的电器设备(如SwitchDevice
、IncandescentLamp
、FluorescentLamp
等)都继承自它。它提供了通用的电气属性和行为(如inputVoltage
、outputVoltage
、updateState()
等),由子类具体实现。ControlDevice
是另一个抽象类,用于表示可控制的设备(如StepDimmer
和ContinuousDimmer
)。它扩展了ElectricDevice
的功能,并专注于电气设备的控制功能。
2. 聚合关系
MutualExclusiveSwitch
与分支电阻:MutualExclusiveSwitch
聚合了一个Map<Integer, Double>
,用于表示各个分支的电阻。每次切换状态时,开关选择一个特定的分支来连接。CurtainDevice
与光照量:CurtainDevice
与光照量(totalLux
)关联。窗帘设备通过光照强度调整自身的开合程度。
3. 依赖关系
Main
类依赖所有设备:Main
是测试类,用于实例化、控制、和测试所有设备类的功能逻辑。ControlledDevice
和具体控制设备:例如StepDimmer
和ContinuousDimmer
,它们是ControlledDevice
的子类,在系统中被用作电气控制单元,控制其他电器设备的状态。
4. 组合关系
FloorFan
和CeilingFan
:两者是ControlledDevice
的实现类,与其组合使用,从而实现对风扇速度的直接控制。SwitchDevice
和电路控制:SwitchDevice
的开关状态直接决定电路的开合,是与整体系统紧密结合的一部分。
5. 接口实现
部分功能(如 getStatus()
和 updateState()
)通过基类(ElectricDevice
和 ControlDevice
)定义接口,由具体实现类(如 StepDimmer
、FluorescentLamp
、FloorFan
)提供具体实现。
设计心得
在开发 家居强电电路模拟程序 的过程中,我深入体验了面向对象设计的优势与挑战。这一项目不仅要求对电路理论有深入理解,还需要将复杂的电路逻辑高效地转化为可维护、可扩展的代码结构。以下是我在开发过程中积累的一些心得体会:
1. 面向对象设计的力量
在程序中,所有电气设备(如开关、调速器、灯具、风扇等)都继承自一个共同的抽象基类 ElectricDevice
。这种设计让我深刻体会到:
- 抽象简化复杂性:通过将设备的通用属性和方法(如
id
、inputVoltage
、outputVoltage
、updateState
等)抽象到基类中,减少了重复代码,提高了代码的可读性和可维护性。 - 多态性带来的灵活性:不同设备的具体行为(如
SwitchDevice
的切换状态,IncandescentLamp
的光照强度计算)通过重写基类的方法实现,允许在处理设备集合时无需关心具体类型,增强了代码的灵活性和扩展性。
启发:面向对象的设计不仅有助于代码的组织和管理,还为后续功能的扩展提供了坚实的基础。通过合理的抽象和继承,可以大幅提升代码的可复用性和可维护性。
2. 电路拓扑的建模
在实现串联和并联电路的过程中,我学到了如何有效地将实际电路拓扑转化为程序中的数据结构:
- 串联电路 (
SerialCircuit
):通过记录每个连接点的信息(如[K1-1, H1-2]
),有效地表示设备之间的串联关系。这种结构使得电压传播和电流计算变得直观。 - 并联电路 (
ParallelCircuit
):管理多个串联电路的并联关系,允许复杂电路的嵌套和扩展。通过递归处理并联电路中的子电路,确保电压和电流的正确分配。
-启发:在建模复杂系统时,选择合适的数据结构至关重要。通过清晰地定义串联和并联电路的关系,可以更高效地实现电压传播和设备状态的更新。
3. 电压传播与状态更新
VoltagePropagator
类负责电压在电路中的传播,确保每个设备的输入和输出电压正确计算。这部分设计让我认识到:
- 电压和电流的物理原理:理解欧姆定律和串并联电路的基本原理是实现正确电压传播的基础。通过代码将这些物理概念准确地转化为计算逻辑,是一个挑战也是学习的机会。
- 递归处理复杂电路:在处理嵌套的并联电路时,递归方法能够简化电路的遍历和计算过程。然而,这也带来了潜在的性能问题,需要在实现时注意优化。
启发:将物理电路原理与编程逻辑结合,需要深刻理解两者的对应关系。通过模块化和递归等编程技巧,可以有效地管理和计算复杂电路中的电压和电流分布。
4. *命令处理与控制逻辑
CommandProcessor
类负责解析和执行用户输入的控制命令,如切换开关状态、调节调速器等。这部分设计让我体会到:
- 命令模式的应用:通过将不同类型的命令(如
#K
、#H
、#F
、#L
)解析并映射到具体的设备操作,实现了控制逻辑的集中管理和扩展。 - 正则表达式的使用:在解析复杂的命令格式时,正则表达式提供了一种高效且灵活的方式,确保命令能够被准确识别和处理。
启发:设计清晰的命令解析和处理机制,可以显著提升程序的可用性和扩展性。通过灵活的解析方法,可以方便地添加新的命令类型和设备操作。
5. 输出生成与结果展示
OutputGenerator
类负责根据设备状态生成最终输出,确保结果按照指定顺序和格式展示。这部分设计让我意识到:
- 数据分类与排序:通过将设备按照类型分类,并按编号排序,确保输出结果的有序性和可读性。这对于用户理解电路状态至关重要。
- 状态展示的多样性:不同设备的状态展示方式各异,如灯具显示光照强度,风扇显示转速,窗帘显示打开百分比。通过统一的接口(
getStatus
方法),简化了状态获取和展示的流程。
启发:设计统一的状态获取接口,可以简化输出生成的逻辑。同时,合理的数据分类和排序,可以提升结果展示的清晰度和用户体验。
6. 异常处理与默认行为
在 CircuitBuilder
中,对于未知设备类型,提供了一个默认的 ControlledDevice
实现,确保程序的健壮性。这让我认识到:
- 防御性编程:在面对可能的未知或错误输入时,提供合理的默认行为,可以防止程序崩溃,提高系统的稳定性。
- 错误信息的传递:虽然默认设备不具备具体功能,但通过合理的状态展示(如
@DeviceID:0
),确保用户能够意识到存在未知设备,需要进一步处理。
启发:在设计系统时,必须考虑到各种可能的异常情况。通过合理的默认处理和错误信息传递,可以提升系统的鲁棒性和用户体验。
家居强电电路模拟程序-4
任务描述
家居强电电路模拟程序的第四版真是让我经历了一场电路烧脑!相比之前的版本,这次的任务不仅更复杂,还新增了不少功能,比如管脚电压的显示、电流限制、短路检测,以及新增的二极管元件。整个过程中,我遇到了不少挑战,但也从中学到了很多东西。
1)增加管脚电压的显示
一开始,任务要求我在输出每个电器的状态信息后,还要显示每个管脚的电压。听起来挺简单的,但实际操作起来却相当麻烦。我不得不重新设计设备类,为每个设备增加管脚属性,并确保在更新状态时能够准确记录每个管脚的电压。这不仅增加了代码的复杂性,还让我在调试过程中花了不少时间,尤其是在确保每个管脚电压都能正确显示方面,真是头疼不已。
2)电流限制
接下来是电流限制的功能。每个设备都有一个最大电流限制,当电流超过这个限制时,必须在输出信息中提示“exceeding current limit error”。为此,我为每个设备添加了一个最大电流属性,并在状态更新时进行检查。实现这个功能时,最大的难点在于如何在保持输出格式整洁的同时,准确地附加错误提示。尤其是在处理多层嵌套电路时,确保每个设备的电流都被正确监测,真的让我头大。
3)短路检测
短路检测功能更是给我带来了不小的挑战。要检测电路中是否出现短路,并在短路发生时只输出“short circuit error”,这需要我在电路状态更新的逻辑中加入复杂的判断条件。尤其是在并联电路中嵌套并联电路时,确保短路能够被准确检测到,避免遗漏任何一个可能导致短路的路径。这部分的实现让我对电路的工作原理有了更深的理解,但也花费了大量的时间去调试和验证。
4)并联电路中包含并联
在本次迭代中,还要处理并联电路中包含其他并联电路的情况。这意味着电阻计算和电流分配需要更加灵活和递归。举个例子,如输入中的并联电路M2,其中包含了另一个并联电路M1,这样的嵌套结构大大增加了计算的复杂性。我不得不优化电阻计算的方法,使用递归来处理多层嵌套的电路结构。在实现过程中,逻辑的复杂性和调试的困难让我一度怀疑自己是否能搞定。
5)二极管
最后,新增的二极管元件让整个项目更加贴近真实电路。二极管具有单向导通和双向截止的特性,这要求我在设备类中增加对电流方向的判断,并根据电压差动态调整二极管的状态和电阻。当电流从左至右流过时,二极管导通,电阻为0;反之,则截止,电阻无穷大。更麻烦的是,当两端电压相等时,二极管的状态还要根据接入方向来决定。这部分的逻辑实现相当复杂,调试时经常出错,让我花费了大量时间来理清电流方向和状态变化的关系。
总结
整个项目过程中,我经历了从设计类结构、处理复杂电路拓扑,到实现细致的电流管理和设备特性模拟的多个挑战。虽然过程中遇到了不少困难和挫折,比如调试复杂逻辑、优化递归算法、确保电流限制和短路检测的准确性,但每解决一个问题,我都感到无比的成就感。这次迭代不仅提升了程序的功能和复杂度,也让我在编程能力和电路知识上有了显著的进步。尽管最终得分只有45分,但这个过程中的学习和成长对我来说是无价的。接下来的版本中,我计划进一步优化程序性能,并引入更多智能化的电路管理功能,让这个模拟程序变得更加完善和实用。
类设计
1. BaseCircuit
类
特色代码:
abstract class BaseCircuit {protected String deviceId;protected String serialNum;protected double sharedInput;protected double sharedOutput;protected double internalResistance;protected double flow;protected boolean sideNearPower;protected boolean fakeStatusFlag = false;public BaseCircuit(String deviceId, String serialNum, double resVal) {double tmpVal = 0;this.deviceId = deviceId;this.serialNum = serialNum;this.sharedInput = tmpVal;this.sharedOutput = tmpVal;this.internalResistance = resVal;this.flow = tmpVal;this.sideNearPower = true;}public abstract void updateState(double inV, double outV);public abstract void updateState(double inV, double outV, double aFlow);public abstract String getStatus();// Getter and setter methods...
}
分析:
BaseCircuit
类是整个电路系统中所有设备的抽象基类。它定义了设备的基本属性和行为,为具体的设备类提供了统一的接口和框架。这种设计遵循了面向对象编程中的抽象和继承原则,使得不同类型的设备可以共享公共的属性和方法,同时又能根据自身的特性实现具体的功能。
主要属性:
deviceId
:设备的唯一标识符,用于在电路中区分不同的设备。serialNum
:设备的序列号,进一步标识设备的唯一性。sharedInput
和sharedOutput
:共享的输入和输出电压,用于在设备之间传递电压信息。internalResistance
:设备的内部电阻,影响电流的流动。flow
:电流流量,表示通过设备的电流强度。sideNearPower
:布尔值,表示设备连接的哪一侧靠近电源。fakeStatusFlag
:一个用于模拟或标记设备状态的标志位,当前默认值为false
。
主要方法:
-
构造函数:
BaseCircuit(String deviceId, String serialNum, double resVal)
初始化设备的基本属性,包括设备ID、序列号和内部电阻。同时,将共享的输入输出电压和电流流量初始化为0,设置设备连接的一侧靠近电源。 -
抽象方法:
updateState(double inV, double outV)
:用于更新设备的电压状态,具体实现由子类定义。updateState(double inV, double outV, double aFlow)
:重载的方法,除了电压外,还更新电流流量。getStatus()
:获取设备的当前状态信息,具体实现由子类定义。
-
具体方法:
getDeviceId()
和getSerialNum()
:获取设备的ID和序列号。getSharedOutput()
:获取共享的输出电压。setSharedIn(double val)
:设置共享的输入电压。getResVal()
:获取设备的内部电阻。changeDirection()
:改变设备连接方向的逻辑,当前示例中由于条件永远为false
,实际不会改变方向。
设计思路:
-
统一接口:通过抽象类
BaseCircuit
,确保所有具体设备类都实现了updateState
和getStatus
方法,使得电路管理系统可以以统一的方式处理不同类型的设备。 -
代码复用:将公共属性和方法放在基类中,避免在每个具体设备类中重复编写相同的代码,提高代码的可维护性和扩展性。
-
灵活扩展:具体设备类(如
MySwitch
、LightBulb
等)继承自BaseCircuit
,可以根据各自的功能需求实现不同的行为和状态管理。 -
封装性:通过
protected
访问修饰符,基类的属性可以被子类直接访问和修改,但对外部类隐藏了内部实现细节,增强了代码的安全性和模块化。
2. ControllerCircuit
类
特色代码:
abstract class ControllerCircuit extends BaseCircuit {public ControllerCircuit(String deviceId, String serialNum) {super(deviceId, serialNum, 0.0);}
}
分析:
ControllerCircuit
类是 BaseCircuit
的抽象子类,专门用于控制类设备。它继承了 BaseCircuit
的所有属性和方法,并为控制类设备提供了一个更具体的抽象层。
主要特点:
-
内部电阻初始化为0:通过构造函数,
ControllerCircuit
将internalResistance
初始化为0.0
,这意味着控制类设备在电路中不引入额外的电阻。 -
抽象类:作为抽象类,
ControllerCircuit
不能被实例化,只能被具体的控制类设备继承。这为进一步的扩展提供了基础,使得所有控制类设备都具有统一的基本特性。
设计思路:
-
层次化设计:通过创建
ControllerCircuit
类,系统实现了设备分类管理,将控制类设备和负载类设备分开处理,增强了系统的可读性和可维护性。 -
代码结构清晰:明确了控制类设备的共性,避免了在多个控制设备类中重复定义相同的初始化逻辑。
3. LoadCircuit
类
特色代码:
abstract class LoadCircuit extends BaseCircuit {public LoadCircuit(String deviceId, String serialNum, double resVal) {super(deviceId, serialNum, resVal);}
}
分析:
LoadCircuit
类是 BaseCircuit
的另一个抽象子类,专门用于负载类设备。与 ControllerCircuit
类似,它为负载类设备提供了一个更具体的抽象层。
主要特点:
-
内部电阻可变:通过构造函数,
LoadCircuit
允许在创建时指定internalResistance
,这使得不同的负载设备可以具有不同的电阻值,反映其在电路中的实际行为。 -
抽象类:作为抽象类,
LoadCircuit
不能被实例化,只能被具体的负载类设备继承。
设计思路:
-
分类管理:通过
LoadCircuit
类,将负载类设备与控制类设备区分开来,使得电路管理更加有序和清晰。 -
灵活性:允许在创建负载设备时指定不同的内部电阻,增强了系统对不同负载设备的支持能力。
4. MySwitch
类
特色代码:
class MySwitch extends ControllerCircuit {public boolean on;private int dummyCount = 0;public MySwitch(String deviceId, String serialNum) {super(deviceId, serialNum);this.on = false;}public void toggle() {on = !on;dummyCount++;if (dummyCount > 9999) {dummyCount = 0;}}@Overridepublic void updateState(double inV, double outV) {double localIn = inV;double localOut = on ? inV : outV;this.sharedInput = localIn;this.sharedOutput = localOut;}@Overridepublic void updateState(double inV, double outV, double aFlow) {if (dummyCount < 0) {on = true;}this.sharedInput = inV;if (on) {this.sharedOutput = inV;} else {this.sharedOutput = 0;}this.flow = aFlow;}@Overridepublic String getStatus() {String status = on ? "closed" : "turned on";String volt = sideNearPower? ((int) sharedInput + "-" + (int) sharedOutput): ((int) sharedOutput + "-" + (int) sharedInput);String limitCurrent = (this.flow > 20) ? "exceeding current limit error" : "";return (status + " " + volt + " " + limitCurrent).trim();}
}
分析:
MySwitch
类是 ControllerCircuit
的具体实现,代表一个可切换的开关设备。它通过继承自 ControllerCircuit
,进一步继承了 BaseCircuit
的属性和方法。
主要属性:
on
:布尔值,表示开关的当前状态(开启或关闭)。dummyCount
:一个内部计数器,用于模拟或记录开关的切换次数。
主要方法:
-
构造函数:初始化开关的状态为关闭(
on = false
)。 -
toggle()
方法:切换开关的状态(从开启到关闭,或从关闭到开启),并更新dummyCount
。当dummyCount
超过9999
时,重置为0
。 -
updateState
方法:- 无流量参数:根据开关状态设置
sharedOutput
。如果开关开启,输出电压等于输入电压;否则,输出电压为outV
。 - 有流量参数:除了更新电压外,还根据开关状态更新电流流量。如果开关关闭,电流流量设为
0
。
- 无流量参数:根据开关状态设置
-
getStatus()
方法:返回开关的当前状态信息,包括开关状态、电压信息以及是否存在电流过载的警告。
设计思路:
-
状态管理:通过
on
属性和toggle()
方法,实现了开关设备的基本状态管理功能。 -
状态反馈:
getStatus()
方法提供了设备当前状态的详细信息,便于电路系统监控和调试。 -
继承与扩展:通过继承
ControllerCircuit
和BaseCircuit
,MySwitch
复用了基础属性和方法,同时根据具体需求实现了特定的行为。
5. MySPDT
类
特色代码:
class MySPDT extends ControllerCircuit {protected boolean on;protected double output2;protected double output3;protected boolean direction;private double dummyTmp = 1.0;public MySPDT(String deviceId, String serialNum) {super(deviceId, serialNum);this.on = true;this.internalResistance = 5.0;this.direction = true;fakeWaterMethod();}public void toggle() {on = !on;this.internalResistance = 10.0;dummyTmp += 0.1;if (dummyTmp > 100) dummyTmp = 1.0;}@Overridepublic void changeDirection() {if (dummyTmp < 0) {return;}if (direction) {this.sideNearPower = false;}this.direction = false;}@Overridepublic void updateState(double inV, double outV) {for (int i = 0; i < 0; i++) {dummyTmp++;}}@Overridepublic void updateState(double inV, double outV, double aFlow) {if (false) {dummyTmp--;}}public void updateState(String pin, Double inV, double outV) {// Implementation based on pin and sideNearPower}public void updateState(String pin, double inV, double outV, double aFlow) {// Implementation based on pin and sideNearPower}@Overridepublic String getStatus() {String status = on ? "closed" : "turned on";String volt = (int) this.sharedInput + "-" + (int) this.output2 + "-" + (int) this.output3;String limitCurrent = this.flow > 20 ? "exceeding current limit error" : "";return (status + " " + volt + " " + limitCurrent).trim();}private void fakeWaterMethod() {int waterMark = 0;for(int i = 0; i < 3; i++) {waterMark += i;}if (waterMark > 10) {waterMark = 10;}}
}
分析:
MySPDT
类是 ControllerCircuit
的具体实现,代表一个单刀双掷(Single Pole Double Throw,SPDT)开关。它具有多个输出端口和方向控制能力。
主要属性:
on
:布尔值,表示开关的当前状态。output2
和output3
:双输出端口的电压值。direction
:布尔值,表示开关的连接方向。dummyTmp
:一个内部变量,用于模拟或记录开关的状态变化。
主要方法:
-
构造函数:初始化开关状态为开启(
on = true
),设置内部电阻为5.0
,方向为true
,并调用fakeWaterMethod()
方法。 -
toggle()
方法:切换开关状态,更新内部电阻,并调整dummyTmp
的值。 -
changeDirection()
方法:根据dummyTmp
和当前方向,改变设备连接的方向。 -
updateState
方法:- 无流量参数:当前实现为空循环,不执行任何操作。
- 有流量参数:当前实现中包含一个永远不执行的条件语句。
- 带有
pin
参数的重载方法:根据引脚信息和sideNearPower
状态,更新不同输出端口的电压和电流流量。
-
getStatus()
方法:返回开关的当前状态信息,包括开关状态、电压信息以及是否存在电流过载的警告。 -
fakeWaterMethod()
方法:一个模拟方法,用于演示内部逻辑(在当前实现中,实际上没有实际作用)。
设计思路:
-
多输出管理:通过
output2
和output3
属性,实现了一个开关具有多个输出端口的功能,适用于更复杂的电路连接。 -
方向控制:
direction
属性和changeDirection()
方法允许设备根据内部状态改变连接方向,增加了设备的灵活性和功能性。 -
状态模拟:
dummyTmp
和fakeWaterMethod()
提供了模拟内部状态变化的能力,有助于测试和调试。 -
继承与扩展:通过继承
ControllerCircuit
和BaseCircuit
,MySPDT
复用了基础属性和方法,同时根据具体需求实现了特定的行为。
6. DividerControl
类
特色代码:
class DividerControl extends ControllerCircuit {protected int currentGear;protected Map<Integer, Double> gearMap = new HashMap<>();private boolean doNothingFlag = true;public DividerControl(String deviceId, String serialNum) {super(deviceId, serialNum);this.currentGear = 0;gearMap.put(0, 0.0);gearMap.put(1, 0.3);gearMap.put(2, 0.6);gearMap.put(3, 0.9);}private int indexConverter(int i) {return i;}public void turnUpGear() {int idx = indexConverter(currentGear);if (idx < gearMap.size() - 1) {currentGear = idx + 1;}}public void turnDownGear() {int idx = indexConverter(currentGear);if (idx > 0) {currentGear = idx - 1;}}@Overridepublic void updateState(double inV, double outV) {double tmpVal = inV - outV;if (doNothingFlag) {if (tmpVal > 9999) tmpVal = 0;tmpVal++;if(tmpVal > 0)return;}this.sharedInput = inV;this.sharedOutput = outV;}@Overridepublic void updateState(double inV, double outV, double aFlow) {this.sharedInput = inV;double ratio = gearMap.getOrDefault(currentGear, 0.0);this.sharedOutput = inV * ratio;this.flow = aFlow;}@Overridepublic String getStatus() {String status = String.valueOf(currentGear);String volt;if (sideNearPower) {volt = (int) this.sharedInput + "-" + (int) this.sharedOutput;} else {volt = (int) this.sharedOutput + "-" + (int) this.sharedInput;}String limitCurrent = this.flow > 18 ? "exceeding current limit error" : "";return (status + " " + volt + " " + limitCurrent).trim();}
}
分析:
DividerControl
类是 ControllerCircuit
的具体实现,代表一个分压控制器。它通过调整齿轮档位来控制输出电压的比例。
主要属性:
currentGear
:当前的齿轮档位,表示分压比例的等级。gearMap
:一个映射表,将齿轮档位映射到相应的分压比例。doNothingFlag
:一个布尔标志,用于控制某些操作逻辑。
主要方法:
-
构造函数:初始化
currentGear
为0
,并设置不同档位对应的分压比例(0.0、0.3、0.6、0.9)。 -
turnUpGear()
和turnDownGear()
方法:分别用于提高和降低齿轮档位,调整分压比例。 -
updateState
方法:- 无流量参数:在
doNothingFlag
为true
时,方法会提前返回,阻止状态更新。 - 有流量参数:根据当前齿轮档位从
gearMap
获取分压比例,并更新输出电压和电流流量。
- 无流量参数:在
-
getStatus()
方法:返回当前档位、电压信息以及是否存在电流过载的警告。
设计思路:
-
分压控制:通过齿轮档位和分压比例的映射,实现了对输出电压的精确控制,适用于需要调节电压的场景。
-
灵活性与可扩展性:
gearMap
提供了灵活的分压比例设置,可以根据需求轻松添加或修改档位和比例。 -
状态反馈:
getStatus()
方法提供了设备当前档位和电压状态的信息,便于系统监控和调试。 -
继承与扩展:通过继承
ControllerCircuit
和BaseCircuit
,DividerControl
复用了基础属性和方法,同时实现了特定的分压控制逻辑。
7. SmoothControl
类
特色代码:
class SmoothControl extends ControllerCircuit {protected double gearRatio;private double dummyIndex;public SmoothControl(String deviceId, String serialNum) {super(deviceId, serialNum);this.gearRatio = 0.0;this.dummyIndex = 1.0;}public void setGearRatio(double ratio) {this.gearRatio = ratio;dummyIndex *= ratio;if (dummyIndex > 9999) dummyIndex = 1.0;}@Overridepublic void updateState(double inV, double outV) {this.sharedInput = inV;this.sharedOutput = outV;}@Overridepublic void updateState(double inV, double outV, double aFlow) {this.sharedInput = inV;this.sharedOutput = inV * gearRatio;this.flow = aFlow;}@Overridepublic String getStatus() {String status = String.format("%.2f", gearRatio);String volt = sideNearPower? ((int) this.sharedInput + "-" + (int) this.sharedOutput): ((int) this.sharedOutput + "-" + (int) this.sharedInput);String limitCurrent = this.flow > 18 ? "exceeding current limit error" : "";return (status + " " + volt + " " + limitCurrent).trim();}
}
分析:
SmoothControl
类是 ControllerCircuit
的具体实现,代表一个平滑控制器。它通过设置齿轮比例来控制输出电压的平滑度。
主要属性:
gearRatio
:齿轮比例,用于调节输出电压的平滑度。dummyIndex
:一个内部变量,用于模拟或记录比例的变化。
主要方法:
-
构造函数:初始化
gearRatio
为0.0
,dummyIndex
为1.0
。 -
setGearRatio(double ratio)
方法:设置新的齿轮比例,并更新dummyIndex
。当dummyIndex
超过9999
时,重置为1.0
。 -
updateState
方法:- 无流量参数:简单地将输入电压赋值给共享输入,并将输出电压赋值给共享输出。
- 有流量参数:根据当前齿轮比例调整输出电压,并更新电流流量。
-
getStatus()
方法:返回当前齿轮比例、电压信息以及是否存在电流过载的警告。
设计思路:
-
平滑控制:通过齿轮比例的设置,实现对输出电压的平滑控制,适用于需要渐变电压输出的场景。
-
状态模拟:
dummyIndex
提供了内部状态变化的模拟能力,有助于测试和调试。 -
状态反馈:
getStatus()
方法提供了设备当前齿轮比例和电压状态的信息,便于系统监控和调试。 -
继承与扩展:通过继承
ControllerCircuit
和BaseCircuit
,SmoothControl
复用了基础属性和方法,同时实现了特定的平滑控制逻辑。
8. LightBulb
类
特色代码:
class LightBulb extends LoadCircuit {protected double brightness;private double dummyStore;public LightBulb(String deviceId, String serialNum) {super(deviceId, serialNum, 10.0);this.brightness = 0;this.dummyStore = 0;}@Overridepublic void updateState(double inV, double outV) {this.sharedInput = inV;this.sharedOutput = outV;dummyStore++;if (dummyStore > 9999) dummyStore = 0;}@Overridepublic void updateState(double inV, double outV, double aFlow) {double voldrop = inV - outV;if (voldrop < 10) {brightness = 0;} else if (voldrop > 220) {brightness = 200;} else {brightness = (int) (((voldrop - 10) * 150) / 210 + 50);}this.sharedInput = inV;this.sharedOutput = outV;this.flow = aFlow;}@Overridepublic String getStatus() {String status = String.format("%.0f", brightness);String volt = sideNearPower? ((int) this.sharedInput + "-" + (int) this.sharedOutput): ((int) this.sharedOutput + "-" + (int) this.sharedInput);String limitCurrent = this.flow > 9 ? "exceeding current limit error" : "";return (status + " " + volt + " " + limitCurrent).trim();}
}
分析:
LightBulb
类是 LoadCircuit
的具体实现,代表一个灯泡负载。它通过电压降和电流流量来控制灯泡的亮度。
主要属性:
brightness
:表示灯泡的亮度值。dummyStore
:一个内部变量,用于模拟或记录状态变化。
主要方法:
-
构造函数:初始化灯泡的内部电阻为
10.0
,亮度为0
,并初始化dummyStore
。 -
updateState
方法:- 无流量参数:简单地将输入电压赋值给共享输入,并将输出电压赋值给共享输出。同时,递增
dummyStore
,当其超过9999
时重置为0
。 - 有流量参数:根据电压降(
voldrop = inV - outV
)计算亮度:- 当电压降小于
10
时,亮度为0
。 - 当电压降大于
220
时,亮度为200
。 - 其他情况下,根据线性关系计算亮度值。
- 当电压降小于
- 无流量参数:简单地将输入电压赋值给共享输入,并将输出电压赋值给共享输出。同时,递增
-
getStatus()
方法:返回灯泡的当前亮度、电压信息以及是否存在电流过载的警告。
设计思路:
-
亮度控制:通过电压降和电流流量的变化,动态调整灯泡的亮度,实现对负载状态的精确控制。
-
状态模拟:
dummyStore
提供了内部状态变化的模拟能力,有助于测试和调试。 -
状态反馈:
getStatus()
方法提供了设备当前亮度和电压状态的信息,便于系统监控和调试。 -
继承与扩展:通过继承
LoadCircuit
和BaseCircuit
,LightBulb
复用了基础属性和方法,同时实现了特定的亮度控制逻辑。
9. MyDiodeTube
类
特色代码:
class MyDiodeTube extends LoadCircuit {protected boolean on;public MyDiodeTube(String deviceId, String serialNum) {super(deviceId, serialNum, 0.0);this.on = true;}public void toggle() {this.on = !on;this.internalResistance = on ? 0.0 : Double.MAX_VALUE;}@Overridepublic void updateState(double inV, double outV) {this.sharedInput = inV;this.sharedOutput = on ? inV : outV;}@Overridepublic void updateState(double inV, double outV, double ampFlow) {this.sharedInput = inV;this.sharedOutput = on ? inV : outV;this.flow = on ? ampFlow : 0.0;}@Overridepublic String getStatus() {String status = on ? "conduction" : "cutoff";String volt = sideNearPower? ((int) this.sharedInput + "-" + (int) this.sharedOutput): ((int) this.sharedOutput + "-" + (int) this.sharedInput);String limitCurrent = (this.flow > 8) ? "exceeding current limit error" : "";return (status + " " + volt + " " + limitCurrent).trim();}
}
分析:
MyDiodeTube
类是 LoadCircuit
的具体实现,代表一个二极管管负载。它根据状态决定是否导通,进而影响电流的流动。
主要属性:
on
:布尔值,表示二极管管的当前状态(导通或截止)。
主要方法:
-
构造函数:初始化二极管管的内部电阻为
0.0
,状态为导通(on = true
)。 -
toggle()
方法:切换二极管管的状态,并根据状态更新内部电阻:- 导通状态:内部电阻为
0.0
,允许电流流动。 - 截止状态:内部电阻为
Double.MAX_VALUE
,阻断电流流动。
- 导通状态:内部电阻为
-
updateState
方法:- 无流量参数:根据当前状态设置输出电压。如果导通,输出电压等于输入电压;否则,输出电压为
outV
。 - 有流量参数:除了更新输出电压外,还根据状态更新电流流量。如果导通,电流流量设为
ampFlow
;否则,电流流量设为0.0
。
- 无流量参数:根据当前状态设置输出电压。如果导通,输出电压等于输入电压;否则,输出电压为
-
getStatus()
方法:返回二极管管的当前状态信息,包括状态(导通或截止)、电压信息以及是否存在电流过载的警告。
设计思路:
-
状态控制:通过
on
属性和toggle()
方法,实现了二极管管的基本状态控制功能,决定电流是否能够流过。 -
高阻抗状态:在截止状态下,内部电阻设置为
Double.MAX_VALUE
,有效地阻断电流流动,模拟真实二极管的截止特性。 -
状态反馈:
getStatus()
方法提供了设备当前状态和电压信息,便于系统监控和调试。 -
继承与扩展:通过继承
LoadCircuit
和BaseCircuit
,MyDiodeTube
复用了基础属性和方法,同时实现了特定的导通与截止逻辑。
10. TubeLight
类
特色代码:
class TubeLight extends LoadCircuit {protected int brightness;public TubeLight(String deviceId, String serialNum) {super(deviceId, serialNum, 5);this.brightness = 0;}@Overridepublic void updateState(double inV, double outV) {this.sharedInput = inV;this.sharedOutput = outV;}@Overridepublic void updateState(double inV, double outV, double ampFlow) {double voldrop = inV - outV;brightness = voldrop > 0 ? 180 : 0;this.sharedInput = inV;this.sharedOutput = outV;this.flow = ampFlow;}@Overridepublic String getStatus() {String status = String.valueOf(brightness);String volt = sideNearPower? ((int) this.sharedInput + "-" + (int) this.sharedOutput): ((int) this.sharedOutput + "-" + (int) this.sharedInput);String limitCurrent = this.flow > 5 ? "exceeding current limit error" : "";return (status + " " + volt + " " + limitCurrent).trim();}
}
分析:
TubeLight
类是 LoadCircuit
的具体实现,代表一个管状灯具负载。它通过电压降和电流流量来控制灯具的亮度。
主要属性:
brightness
:表示管状灯具的亮度值。
主要方法:
-
构造函数:初始化管状灯具的内部电阻为
5.0
,亮度为0
。 -
updateState
方法:- 无流量参数:简单地将输入电压赋值给共享输入,并将输出电压赋值给共享输出。
- 有流量参数:根据电压降(
voldrop = inV - outV
)设置亮度:- 当电压降大于
0
时,亮度设为180
。 - 否则,亮度设为
0
。 - 更新共享输入、输出电压和电流流量。
- 当电压降大于
-
getStatus()
方法:返回管状灯具的当前亮度、电压信息以及是否存在电流过载的警告。
设计思路:
-
亮度控制:通过电压降和电流流量的变化,动态调整管状灯具的亮度,实现对负载状态的精确控制。
-
状态反馈:
getStatus()
方法提供了设备当前亮度和电压状态的信息,便于系统监控和调试。 -
继承与扩展:通过继承
LoadCircuit
和BaseCircuit
,TubeLight
复用了基础属性和方法,同时实现了特定的亮度控制逻辑。
11. FanCeiling
类
特色代码:
class FanCeiling extends LoadCircuit {protected double speed;public FanCeiling(String deviceId, String serialNum) {super(deviceId, serialNum, 20.0);this.speed = 0;}@Overridepublic void updateState(double inV, double outV) {this.sharedInput = inV;this.sharedOutput = outV;}@Overridepublic void updateState(double inV, double outV, double ampFlow) {double voldrop = inV - outV;if (voldrop < 80) {speed = 0;} else if (voldrop > 150) {speed = 360;} else {speed = (int) (4 * voldrop) - 240;}this.sharedInput = inV;this.sharedOutput = outV;this.flow = ampFlow;}@Overridepublic String getStatus() {String status = String.format("%.0f", speed);String volt = sideNearPower? ((int) this.sharedInput + "-" + (int) this.sharedOutput): ((int) this.sharedOutput + "-" + (int) this.sharedInput);String limitCurrent = this.flow > 12 ? "exceeding current limit error" : "";return (status + " " + volt + " " + limitCurrent).trim();}
}
分析:
FanCeiling
类是 LoadCircuit
的具体实现,代表一个天花板风扇负载。它通过电压降和电流流量来控制风扇的转速。
主要属性:
speed
:表示风扇的当前转速。
主要方法:
-
构造函数:初始化风扇的内部电阻为
20.0
,转速为0
。 -
updateState
方法:- 无流量参数:简单地将输入电压赋值给共享输入,并将输出电压赋值给共享输出。
- 有流量参数:根据电压降(
voldrop = inV - outV
)设置风扇的转速:- 当电压降小于
80
时,转速设为0
。 - 当电压降大于
150
时,转速设为360
。 - 其他情况下,转速根据线性关系计算。
- 当电压降小于
-
getStatus()
方法:返回风扇的当前转速、电压信息以及是否存在电流过载的警告。
设计思路:
-
转速控制:通过电压降和电流流量的变化,动态调整风扇的转速,实现对负载状态的精确控制。
-
状态反馈:
getStatus()
方法提供了设备当前转速和电压状态的信息,便于系统监控和调试。 -
继承与扩展:通过继承
LoadCircuit
和BaseCircuit
,FanCeiling
复用了基础属性和方法,同时实现了特定的转速控制逻辑。
12. FanFloor
类
特色代码:
class FanFloor extends LoadCircuit {protected double speed;public FanFloor(String deviceId, String serialNum) {super(deviceId, serialNum, 20.0);this.speed = 0;}@Overridepublic void updateState(double inV, double outV) {this.sharedInput = inV;this.sharedOutput = outV;}@Overridepublic void updateState(double inV, double outV, double ampFlow) {double voldrop = inV - outV;if (voldrop >= 80 && voldrop < 100) {speed = 80;} else if (voldrop >= 100 && voldrop < 120) {speed = 160;} else if (voldrop >= 120 && voldrop < 140) {speed = 260;} else if (voldrop >= 140) {speed = 360;} else {speed = 0;}this.sharedInput = inV;this.sharedOutput = outV;this.flow = ampFlow;}@Overridepublic String getStatus() {String status = String.format("%.0f", speed);String volt = sideNearPower? ((int) this.sharedInput + "-" + (int) this.sharedOutput): ((int) this.sharedOutput + "-" + (int) this.sharedInput);String limitCurrent = this.flow > 14 ? "exceeding current limit error" : "";return (status + " " + volt + " " + limitCurrent).trim();}
}
分析:
FanFloor
类是 LoadCircuit
的具体实现,代表一个落地风扇负载。它通过电压降和电流流量来控制风扇的转速。
主要属性:
speed
:表示风扇的当前转速。
主要方法:
-
构造函数:初始化风扇的内部电阻为
20.0
,转速为0
。 -
updateState
方法:- 无流量参数:简单地将输入电压赋值给共享输入,并将输出电压赋值给共享输出。
- 有流量参数:根据电压降(
voldrop = inV - outV
)设置风扇的转速:- 当电压降在
80-100
之间,转速设为80
。 - 当电压降在
100-120
之间,转速设为160
。 - 当电压降在
120-140
之间,转速设为260
。 - 当电压降大于或等于
140
时,转速设为360
。 - 否则,转速设为
0
。
- 当电压降在
-
getStatus()
方法:返回风扇的当前转速、电压信息以及是否存在电流过载的警告。
设计思路:
-
多级转速控制:通过多个电压降区间,实现对风扇转速的多级控制,适用于需要不同风速的场景。
-
状态反馈:
getStatus()
方法提供了设备当前转速和电压状态的信息,便于系统监控和调试。 -
继承与扩展:通过继承
LoadCircuit
和BaseCircuit
,FanFloor
复用了基础属性和方法,同时实现了特定的多级转速控制逻辑。
13. WindowCurtain
类
特色代码:
class WindowCurtain extends LoadCircuit {protected double open;protected double lightness;public WindowCurtain(String deviceId, String serialNum) {super(deviceId, serialNum, 15.0);this.open = 1.0;this.lightness = 0.0;}public void updateLightness(double light) {lightness = light;}@Overridepublic void updateState(double inV, double outV) {this.sharedInput = inV;this.sharedOutput = outV;}@Overridepublic void updateState(double inV, double outV, double ampFlow) {double voldrop = inV - outV;if (voldrop < 50) {} else {if (lightness >= 400) {open = 0.0;} else if (lightness >= 300) {open = 0.2;} else if (lightness >= 200) {open = 0.4;} else if (lightness >= 100) {open = 0.6;} else if (lightness >= 50) {open = 0.8;} else {open = 1.0;}}this.sharedInput = inV;this.sharedOutput = outV;this.flow = ampFlow;}@Overridepublic String getStatus() {String status = String.format("%.0f%%", open * 100);String volt = sideNearPower? ((int) this.sharedInput + "-" + (int) this.sharedOutput): ((int) this.sharedOutput + "-" + (int) this.sharedInput);String limitCurrent = this.flow > 12 ? "exceeding current limit error" : "";return (status + " " + volt + " " + limitCurrent).trim();}
}
分析:
WindowCurtain
类是 LoadCircuit
的具体实现,代表一个窗帘负载。它通过光照强度和电压降来控制窗帘的开启程度。
主要属性:
open
:表示窗帘的开启程度,范围为0.0
(完全关闭)到1.0
(完全开启)。lightness
:表示光照强度,用于决定窗帘的开启程度。
主要方法:
-
构造函数:初始化窗帘的内部电阻为
15.0
,开启程度为1.0
(完全开启),光照强度为0.0
。 -
updateLightness(double light)
方法:更新窗帘的光照强度,用于决定窗帘的开启程度。 -
updateState
方法:- 无流量参数:简单地将输入电压赋值给共享输入,并将输出电压赋值给共享输出。
- 有流量参数:根据电压降和光照强度设置窗帘的开启程度:
- 当电压降大于或等于
50
时,根据lightness
设置open
的值:lightness >= 400
:open = 0.0
(完全关闭)。lightness >= 300
:open = 0.2
。lightness >= 200
:open = 0.4
。lightness >= 100
:open = 0.6
。lightness >= 50
:open = 0.8
。- 否则,
open = 1.0
(完全开启)。
- 更新共享输入、输出电压和电流流量。
- 当电压降大于或等于
-
getStatus()
方法:返回窗帘的当前开启程度(百分比)、电压信息以及是否存在电流过载的警告。
设计思路:
-
环境感知控制:通过光照强度 (
lightness
) 和电压降,动态调整窗帘的开启程度,实现智能化控制。 -
状态反馈:
getStatus()
方法提供了窗帘当前开启程度和电压状态的信息,便于系统监控和调试。 -
继承与扩展:通过继承
LoadCircuit
和BaseCircuit
,WindowCurtain
复用了基础属性和方法,同时实现了特定的环境感知控制逻辑。
14. AllCircuit
类
特色代码:
class AllCircuit {private List<String> internalRecord = new ArrayList<>();public Map<String, BaseCircuit> devices = new HashMap<>();public Map<String, List<String>> seriesCircuits = new LinkedHashMap<>();public Map<String, String[]> parallelCircuits = new HashMap<>();public Map<String, Double> voltages = new HashMap<>();// Methods to register devices, add circuits, handle messages, calculate resistance, update devices, etc.
}
分析:
AllCircuit
类是整个电路管理系统的核心类,负责管理所有设备、串联和并联的电路配置,以及处理电路的状态更新和控制消息。
主要属性:
internalRecord
:内部记录列表,用于跟踪设备的注册情况。devices
:一个映射表,将设备ID映射到相应的BaseCircuit
实例。seriesCircuits
:一个映射表,管理串联电路的配置,每个串联电路由一组设备ID组成。parallelCircuits
:一个映射表,管理并联电路的配置,每个并联电路由一组串联电路ID组成。voltages
:一个映射表,存储设备的电压信息。
主要方法:
-
设备管理:
registerDevice(BaseCircuit device)
:注册设备到系统中,确保设备ID的唯一性。
-
电路配置:
addSeriesCircuit(String circuitId, List<String> connections)
:添加一个串联电路配置。addParallelCircuit(String circuitId, String[] series)
:添加一个并联电路配置。
-
消息处理:
handleControlMessage(String message)
:处理来自用户或外部系统的控制消息,执行相应的设备操作(如切换开关、调整齿轮等)。
-
电阻计算:
calculateResistance(String circuitId)
:计算指定电路的总电阻,根据串联和并联的配置递归计算。parallelResistance(String parallelId)
:计算并联电路的总电阻。computeParallelValue(List<Double> arr)
:计算并联电路的总电阻值。
-
电路状态判断:
judgeParallel(String parallelId)
:判断并联电路的状态(正常、短路或开路)。judgeSeries(String circuitId)
:判断串联电路的状态(正常、短路或开路)。
-
设备状态更新:
updateDevices()
:更新所有设备的状态,检测短路或开路情况,并根据电阻和电压更新设备状态。updateParallel(String parallelId, double voltIn, double voltOut, double ampFlow)
:更新并联电路中所有串联电路的状态。updateSeries(String circuitId, double voltIn, double voltOut, double ampFlow)
:更新串联电路中所有设备的状态。updateCurtain()
:专门更新窗帘设备的状态,基于整体光照强度。judgeSPDTswitch()
:专门判断和更新 SPDT 开关的状态。
-
辅助方法:
checkMySwitch(MySwitch sw, String circuitId)
和checkMySPDT(MySPDT sw, int pin, String circuitId)
:用于检查特定控制设备的状态。totalResistanceOfCircuit(String circuitId)
:计算指定电路的总电阻。
设计思路:
-
集中管理:
AllCircuit
类集中管理所有设备和电路配置,提供统一的接口进行设备注册、配置和状态更新。 -
模块化设计:通过将串联和并联电路分开管理,增强了系统的可扩展性和维护性。
-
递归计算:电阻的计算采用递归方法,支持嵌套的串联和并联电路结构,适应复杂电路的需求。
-
状态检测与更新:通过多种判断方法,实时检测电路状态(如短路、开路)并更新设备状态,确保系统的可靠性和安全性。
-
消息驱动:支持通过控制消息(如切换开关、调整齿轮等)来动态改变电路配置和设备状态,实现灵活的电路控制。
15. DeviceMaker
接口
特色代码:
@FunctionalInterface
interface DeviceMaker {BaseCircuit make(String devId, String devNum);
}
分析:
DeviceMaker
是一个函数式接口,定义了一个方法 make
,用于根据设备ID和序列号创建 BaseCircuit
的具体实例。
主要特点:
-
函数式接口:通过
@FunctionalInterface
注解,明确表示该接口只包含一个抽象方法,适用于 Lambda 表达式和方法引用。 -
通用性:
make
方法的签名允许灵活地创建不同类型的设备,只需实现具体的设备创建逻辑。
设计思路:
-
工厂模式:
DeviceMaker
接口作为工厂方法的抽象,支持使用工厂模式创建不同类型的设备实例,增强了系统的灵活性和可扩展性。 -
代码简洁:通过函数式接口,可以使用 Lambda 表达式或方法引用来简化设备创建过程,减少代码冗余。
-
松耦合:将设备创建逻辑与设备管理分离,降低了系统各部分之间的耦合度,提升了代码的可维护性。
16. MyUserInput
类
特色代码:
class MyUserInput {private final Scanner commandScanner = new Scanner(System.in);private final AllCircuit mainCircuit = new AllCircuit();public void handleInput() {String commandLine;do {if (!commandScanner.hasNextLine()) {break;}commandLine = commandScanner.nextLine().trim();if ("end".equals(commandLine)) {break;}// Handling different command prefixesif (commandLine.startsWith("#T")) {handleSeriesCommand(commandLine);} else if (commandLine.startsWith("#M")) {handleParallelCommand(commandLine);} else if (commandLine.startsWith("#")) {mainCircuit.handleControlMessage(commandLine);}} while (!"end".equals(commandLine));}private void handleSeriesCommand(String inputLine) {// Parsing and adding series circuits}private void handleParallelCommand(String inputLine) {// Parsing and adding parallel circuits}private BaseCircuit fabricateCircuit(String keyType, String fullId, String serial, String pinInfo) {// Factory logic using DeviceMaker}private void finalizeDeviceCreation(BaseCircuit device, String devType, boolean hasPin2) {// Finalizing device creation based on type and pin}public AllCircuit obtainCircuit() {return mainCircuit;}
}
分析:
MyUserInput
类负责处理用户输入,解析命令并配置电路系统。它通过读取用户输入的命令,创建和配置相应的设备和电路结构。
主要属性:
commandScanner
:用于读取用户输入的Scanner
对象。mainCircuit
:管理整个电路系统的AllCircuit
实例。
主要方法:
-
handleInput()
方法:循环读取用户输入的命令,直到输入"end"
。根据命令的前缀(如#T
、#M
、#K
等),调用相应的方法处理串联电路、并联电路或控制消息。 -
handleSeriesCommand(String inputLine)
方法:解析并处理串联电路的配置命令,注册设备并将其添加到串联电路中。 -
handleParallelCommand(String inputLine)
方法:解析并处理并联电路的配置命令,将一组串联电路添加到并联电路中。 -
fabricateCircuit(String keyType, String fullId, String serial, String pinInfo)
方法:根据设备类型(如K
、F
、L
等),使用DeviceMaker
接口创建具体的设备实例。 -
finalizeDeviceCreation(BaseCircuit device, String devType, boolean hasPin2)
方法:对创建的设备进行最终的配置,如改变方向或切换状态。 -
obtainCircuit()
方法:返回配置完成的AllCircuit
实例,用于后续的电路管理和状态更新。
设计思路:
-
命令驱动:通过解析用户输入的命令,动态配置电路系统,实现灵活的电路设计和管理。
-
工厂模式应用:利用
DeviceMaker
接口,实现设备的灵活创建,支持多种类型设备的扩展。 -
模块化设计:将串联电路和并联电路的处理逻辑分开,增强了代码的可读性和可维护性。
-
错误处理与健壮性:通过对命令进行前缀判断和条件检查,确保只有有效的命令被处理,提高系统的健壮性。
17. MyDisplayOutput
类
特色代码:
class MyDisplayOutput {private final AllCircuit circuit;private final Map<String, Integer> prefixOrder = new LinkedHashMap<>();public MyDisplayOutput(AllCircuit circuit) {this.circuit = circuit;initOrderMap();}private void initOrderMap() {prefixOrder.put("K", 1);prefixOrder.put("F", 2);prefixOrder.put("L", 3);prefixOrder.put("B", 4);prefixOrder.put("R", 5);prefixOrder.put("D", 6);prefixOrder.put("A", 7);prefixOrder.put("H", 8);prefixOrder.put("S", 9);prefixOrder.put("P", 10);}private void sortDevices(List<String> deviceList) {deviceList.sort((id1, id2) -> {String prefix1 = id1.substring(0, 1);String prefix2 = id2.substring(0, 1);int order1 = prefixOrder.getOrDefault(prefix1, 999);int order2 = prefixOrder.getOrDefault(prefix2, 999);if (order1 != order2) {return Integer.compare(order1, order2);} else {return id1.compareTo(id2);}});}public void printAll() {boolean shortCircuit = circuit.updateDevices();if (shortCircuit) {return;}Set<String> deviceIdSet = circuit.devices.keySet();List<String> deviceList = new ArrayList<>(deviceIdSet);sortDevices(deviceList);for (String deviceId : deviceList) {BaseCircuit dev = circuit.devices.get(deviceId);if (dev == null) {continue;}System.out.println("@" + dev.deviceId + ":" + dev.getStatus());}}
}
分析:
MyDisplayOutput
类负责将电路系统的当前状态输出到用户界面或控制台。它从 AllCircuit
类中获取设备信息,并按照特定顺序显示每个设备的状态。
主要属性:
circuit
:引用AllCircuit
实例,获取电路系统的设备和状态信息。prefixOrder
:一个映射表,用于定义设备ID前缀的显示顺序,确保输出的设备按照预定的顺序排列。
主要方法:
-
构造函数:接受一个
AllCircuit
实例,并初始化设备ID前缀的显示顺序。 -
initOrderMap()
方法:初始化prefixOrder
映射表,将不同设备类型的前缀与排序顺序关联。 -
sortDevices(List<String> deviceList)
方法:根据prefixOrder
对设备列表进行排序。如果设备的前缀在映射表中未定义,则默认为较高的排序值(999
),确保未定义前缀的设备排在最后。 -
printAll()
方法:- 调用
circuit.updateDevices()
更新所有设备的状态。如果检测到短路,则提前返回。 - 获取所有设备的ID,并按照预定顺序排序。
- 遍历排序后的设备列表,打印每个设备的状态信息,格式为
@设备ID:状态信息
。
- 调用
设计思路:
-
状态展示:通过
printAll()
方法,将电路系统中所有设备的当前状态清晰地展示给用户,便于监控和调试。 -
排序机制:通过
prefixOrder
映射表,实现设备的有序显示,增强了输出信息的可读性和系统性。 -
模块化设计:将状态输出与电路管理分离,使得系统的各个部分职责清晰,易于维护和扩展。
-
错误处理:在检测到短路等严重错误时,提前终止输出,避免展示不准确或不完整的状态信息。
18. Main
类
特色代码:
public class Main {public static void main(String[] args) {MyUserInput input = new MyUserInput();input.handleInput();AllCircuit circuit = input.obtainCircuit();MyDisplayOutput export = new MyDisplayOutput(circuit);export.printAll();}
}
分析:
Main
类是程序的入口点,负责启动整个电路管理系统。它通过 MyUserInput
类处理用户输入,配置电路系统,并通过 MyDisplayOutput
类展示电路的当前状态。
主要方法:
main(String[] args)
方法:-
初始化用户输入处理:创建
MyUserInput
实例并调用handleInput()
方法,开始读取和处理用户输入的命令,配置电路系统。 -
获取配置完成的电路系统:通过
obtainCircuit()
方法获取配置完成的AllCircuit
实例。 -
初始化输出处理:创建
MyDisplayOutput
实例,传入AllCircuit
实例,用于处理和展示电路的当前状态。 -
输出电路状态:调用
printAll()
方法,将电路系统的当前状态输出到控制台或用户界面。
-
设计思路:
-
流程控制:
Main
类通过有序地调用各个组件的方法,确保程序按照预定的流程运行,从输入处理到状态展示。 -
模块化整合:将输入处理和输出展示与电路管理分离,使得系统的各个部分职责清晰,易于维护和扩展。
-
简洁性:
Main
类的设计保持了简单和直接,只负责协调和调用其他类的功能,避免了过多的逻辑嵌入其中。
总结
该电路管理系统通过一系列面向对象的设计原则,实现了对复杂电路的灵活配置和管理。核心设计包括:
-
抽象与继承:通过
BaseCircuit
、ControllerCircuit
和LoadCircuit
等抽象类,定义了设备的基本结构和行为,为具体设备类提供了统一的接口和框架。 -
多态性:具体设备类(如
MySwitch
、LightBulb
等)通过实现抽象方法,实现了各自独特的行为和状态管理,支持多样化的电路配置需求。 -
模块化设计:
AllCircuit
类负责电路的整体管理,MyUserInput
类处理用户输入,MyDisplayOutput
类负责状态展示,Main
类作为程序入口,协调各部分的工作。 -
工厂模式:通过
DeviceMaker
接口,实现了设备的灵活创建,支持系统的可扩展性和灵活性。 -
状态管理与反馈:通过各个设备类的
getStatus()
方法,实时获取设备状态信息,便于系统监控和调试。 -
错误处理与健壮性:在电路状态判断和更新过程中,系统能够检测和处理短路、开路等异常情况,确保电路的安全和可靠运行。
整体而言,该系统展示了面向对象设计在复杂电路管理中的应用,通过合理的类结构和设计模式,实现了高度的可扩展性、可维护性和灵活性。
代码复杂度与质量分析(基于 SourceMonitor)
在第最后一次大作业中,我使用了 SourceMonitor 对代码进行了详细的复杂度分析,这里给出了项目的整体质量指标:
分析图表:
Source Monitor 分析报告
在对代码进行 Source Monitor 分析后,获得了以下代码度量指标。这些指标为我们评估代码质量、复杂性和可维护性提供了有价值的参考。以下是对这些指标的详细分析与反思:
1. 代码规模与结构
- 总行数:1,342 行
- 总语句数:929 语句
- 类和接口数量:17 个
- 每类平均方法数:5.35 个
- 每方法平均语句数:8.22 行
分析与反思:
leitu.java
文件规模较大,包含了17个类和接口,表明项目结构较为复杂。每类平均拥有约5个方法,方法的平均语句数为8.22行,这在一定程度上表明方法的职责相对集中。然而,考虑到项目的复杂性,保持方法的简洁性尤为重要,以提高代码的可读性和可维护性。
改进建议:
- 模块化进一步细化:对于功能较为复杂的类,可以考虑拆分成更小的模块,每个模块专注于单一职责。
- 方法简化:尽量保持每个方法的语句数在10行以内,确保方法职责单一,便于理解和测试。
2. 分支语句与注释比例
- 分支语句百分比:22.9%
- 带注释的行比例:0.0%
分析与反思:
分支语句占比接近23%,表明代码中存在较多的条件判断和控制流。这可能导致代码逻辑较为复杂,增加了理解和维护的难度。同时,注释比例为0%,意味着代码中缺乏必要的文档说明,可能会影响团队协作和后续开发。
改进建议:
- 优化控制流:审查高分支语句比例的代码段,寻找简化逻辑或引入设计模式(如策略模式、状态模式)的方法,以减少分支数量。
- 增加注释:为关键算法、复杂逻辑和类、方法添加适当的注释,提升代码的可读性和可维护性。
3. 代码复杂度分析
- 最大复杂度:52
- 平均复杂度:4.01
- 最大块深度:7
- 平均块深度:2.64
最复杂的方法:
AllCircuit.updateSeries()
:复杂度52,语句数116,最大块深度7,调用次数56次
分析与反思:
AllCircuit.updateSeries()
方法的复杂度高达52,远超一般方法的复杂度阈值(通常建议不超过10)。这种高复杂度通常意味着方法职责过多,逻辑嵌套过深,难以理解和维护。此外,方法的最大块深度为7,表明存在深层嵌套的控制结构,进一步增加了代码的复杂性。
改进建议:
- 方法重构:将
updateSeries()
方法拆分成多个小方法,每个小方法负责特定的子任务,降低单个方法的复杂度。 - 简化嵌套结构:通过提前返回、使用辅助数据结构或引入设计模式,减少嵌套层级,使代码逻辑更加清晰。
4. 方法调用与依赖
- 方法调用语句:311
- 高调用频率的方法:
AllCircuit.updateSeries()
:调用56次AllCircuit.handleControlMessage()
:调用20次AllCircuit.judgeSeries()
:调用14次AllCircuit.judgeParallel()
:调用2次
分析与反思:
高调用频率的方法表明这些方法在系统中起到了关键作用。然而,频繁调用可能导致方法之间的依赖关系过于紧密,增加了系统的耦合度,影响了代码的可测试性和可扩展性。
改进建议:
- 减少耦合:通过引入接口和抽象类,降低方法之间的直接依赖,增强代码的灵活性。
- 优化调用逻辑:审查高调用频率的方法,确保其实现高效,避免不必要的重复调用。
5. 代码深度与块深度
- 最大块深度:7
- 平均块深度:2.64
分析与反思:
块深度反映了代码中嵌套结构的复杂程度。最大块深度为7,显示出部分代码存在深层嵌套,可能导致代码难以理解和维护。平均块深度为2.64,整体来看还算合理,但仍有优化空间。
改进建议:
- 简化嵌套:通过逻辑重构、使用辅助方法或提前返回策略,减少不必要的嵌套层级。
- 增强可读性:将复杂的嵌套逻辑拆分成多个独立的方法,提高代码的可读性和可维护性。
6. 注释与文档
- 带注释的行比例:0.0%
分析与反思:
代码中完全缺乏注释,可能导致代码理解难度增加,尤其是在复杂逻辑和高复杂度方法中,缺乏注释会使得维护和协作更加困难。
改进建议:
- 添加必要注释:为关键部分的代码添加注释,解释复杂的算法逻辑、方法的用途和类的职责。
- 编写文档:为整个项目编写开发文档,详细描述各模块的功能和交互关系,帮助新成员快速上手。
1. Kiviat 图表分析
Kiviat 图表(也称为雷达图)以图形方式展示了多个代码度量指标,使我们能够快速识别代码的优势和不足。从图中可以看出:
- 注释比例(% Comments):该指标几乎为零,表明代码中缺乏注释,这与 Source Monitor 报告中的 0% 注释比例一致。
- 每类方法数(Methods/Class):该指标处于中等水平,表明类的职责划分相对合理,但仍有优化空间。
- 每方法平均语句数(Avg Stmts/Method):该指标也处于中等水平,表明方法长度适中,但仍需注意控制方法复杂度。
- 平均复杂度(Avg Complexity):该指标处于中等水平,表明代码整体复杂度可控,但仍需关注高复杂度方法。
- 平均块深度(Avg Depth):该指标处于中等水平,表明代码的嵌套结构相对合理,但仍需注意避免深层嵌套。
- 最大块深度(Max Depth):该指标较高,表明部分代码存在深层嵌套,需要重点关注。
- 最大复杂度(Max Complexity):该指标明显偏高,表明存在复杂度过高的方法,需要进行重构。
分析与反思:
Kiviat 图表直观地展示了代码的整体特征。注释比例过低、最大复杂度和最大块深度偏高是需要重点关注的问题。这些问题不仅影响代码的可读性和可维护性,还可能导致潜在的错误和性能问题。
改进建议:
- 优先解决高复杂度问题:针对最大复杂度和最大块深度偏高的代码段,进行重构和优化,降低代码的复杂性。
- 增加注释:为关键代码段添加注释,提高代码的可读性和可维护性。
- 优化代码结构:通过模块化设计和设计模式,降低代码的耦合度,提高代码的灵活性。
2. 块直方图分析
块直方图展示了代码中不同块深度的语句数量分布。从图中可以看出:
- 深度为 2 的块:语句数量最多,表明代码中存在大量的双层嵌套结构。
- 深度为 3 和 4 的块:语句数量也较多,表明代码中存在一定程度的嵌套结构。
- 深度为 5、6、7 的块:语句数量逐渐减少,但仍有少量语句存在于深层嵌套中。
- 深度为 8 及以上的块:语句数量为零,表明代码中不存在过深的嵌套结构。
分析与反思:
块直方图清晰地展示了代码中不同嵌套深度的语句分布。大量语句集中在深度为 2 和 3 的块中,表明代码中存在一定程度的嵌套结构。虽然不存在过深的嵌套结构,但仍需注意控制嵌套深度,避免代码过于复杂。
改进建议:
- 减少嵌套层级:针对深度为 3 及以上的块,进行重构和优化,减少嵌套层级,提高代码的可读性。
- 使用辅助方法:将深层嵌套的逻辑拆分成多个辅助方法,降低单个方法的复杂度。
- 提前返回策略:在方法中尽早返回,避免不必要的嵌套,提高代码的简洁性。
3. 综合分析与改进方向
结合 Source Monitor 的文本报告和可视化图表,我们可以得出以下综合结论:
- 代码规模较大:
leitu.java
文件包含 1342 行代码,表明项目规模较大,需要注意代码的组织和管理。 - 注释比例过低:代码中完全缺乏注释,严重影响了代码的可读性和可维护性。
- 存在高复杂度方法:
AllCircuit.updateSeries()
方法的复杂度高达 52,需要进行重构和优化。 - 存在深层嵌套结构:部分代码存在深层嵌套,需要注意控制嵌套深度,提高代码的可读性。
- 方法调用关系复杂:高调用频率的方法表明方法之间的依赖关系较为紧密,需要注意降低耦合度。
改进方向:
- 重构高复杂度方法:优先重构
AllCircuit.updateSeries()
等高复杂度方法,降低系统整体复杂度。 - 增加代码注释:为关键代码段添加注释,解释复杂的算法逻辑、方法的用途和类的职责。
- 优化代码结构:通过模块化设计和设计模式,降低代码的耦合度,提高代码的灵活性。
- 简化嵌套结构:通过提前返回、使用辅助数据结构或引入设计模式,减少嵌套层级,使代码逻辑更加清晰。
- 持续代码审查:定期进行代码审查,及时发现并解决潜在的代码质量问题,保持代码库的健康状态。
总结
通过结合 Source Monitor 的文本报告和可视化图表,我们对 leitu.java
文件的代码质量和复杂性有了更全面的了解。虽然代码在模块化设计方面表现出一定的优势,但仍存在一些需要改进的地方,如注释比例过低、高复杂度方法和深层嵌套结构等。通过有针对性的改进,可以显著提升代码的质量和可维护性,为项目的长期可持续发展奠定坚实的基础。
类图分析(基于 PowerDesigner)
类图关键构成
1. 类 BaseCircuit
属性:
protected String deviceId
:电路的设备ID。protected String serialNum
:序列号,用于唯一标识。protected double sharedInput
:电路的输入电压。protected double sharedOutput
:电路的输出电压。protected double totalResistance
:电路的总电阻。protected boolean isActive
:电路是否激活的状态。
构造方法:
public BaseCircuit(String deviceId)
:初始化设备ID,默认的输入、输出电压和电阻值为 0。
方法:
public abstract void updateState(double in, double out)
:抽象方法,用于更新电路的状态。public double getSharedInput()
:返回输入电压。public double getSharedOutput()
:返回输出电压。public abstract String getStatus()
:获取电路当前状态的字符串。
2. 类 WindowCurtain
属性:
private double open
:窗帘的开启程度(0 到 1)。private double lightness
:窗帘透光率。
构造方法:
public WindowCurtain(String deviceId, String serialNum)
:初始化窗帘的设备ID和序列号,并设置默认的开启程度和透光率。
方法:
public void updateState(double inputVoltage, double outputVoltage)
:根据输入电压更新窗帘的开启程度和透光率。public String getStatus()
:返回窗帘的当前状态字符串,包括开启程度和透光率。
3. 类 SmoothControl
属性:
private double gearRatio
:控制档位的比例值。
构造方法:
public SmoothControl(String deviceId)
:初始化设备ID并设置默认的档位比例。
方法:
public void updateState(double in, double out)
:根据电压调整档位比例。public String getStatus()
:返回控制器的状态字符串,显示当前档位。
4. 类 CommandProcessor
属性:
private Scanner commandScanner
:用于接收命令输入。private AllCircuit mainCircuit
:主电路的引用,用于解析命令。
构造方法:
public CommandProcessor(AllCircuit circuit)
:初始化主电路和命令输入流。
方法:
public void processCommands()
:解析输入的命令并调用相应电路的方法完成操作。
5. 类 MySwitch
属性:
private boolean isOn
:开关是否开启的状态。private int count
:开关切换的计数器。
构造方法:
public MySwitch(String deviceId)
:初始化开关设备ID并设置默认状态。
方法:
public void toggleSwitch()
:切换开关状态。public String getStatus()
:返回当前开关状态字符串。
6. 类 AllCircuit
属性:
private List<String> internalRecord
:记录所有的电路信息。private Map<String, BaseCircuit> devices
:存储所有设备的映射。private Map<String, List<BaseCircuit>> parallelCircuits
:并联电路的映射。private Map<String, List<BaseCircuit>> serialCircuits
:串联电路的映射。
构造方法:
public AllCircuit()
:初始化电路映射和记录器。
方法:
public void registerDevice(BaseCircuit device)
:注册新设备到电路。public void updateDeviceState(String deviceId, double inputVoltage, double outputVoltage)
:更新指定设备的状态。public String getCircuitStatus()
:返回整个电路的状态信息。
7. 类 Main
属性:
private AllCircuit mainCircuit
:主电路对象。
方法:
public static void main(String[] args)
:程序的入口方法,初始化所有设备、解析命令并输出结果。
类之间的关系
以下是主要类之间关系的图示描述(逻辑结构):
- 继承关系:
ElectricDevice
→WindowCurtain
,SmoothControl
等设备类。BaseCircuit
→ 具体电路类(如串联电路、并联电路)。
- 组合关系:
AllCircuit
持有多个BaseCircuit
和设备对象。CommandProcessor
组合了AllCircuit
,用于解析和操作。
- 依赖关系:
Main
依赖于CommandProcessor
和AllCircuit
,实现控制与运行。MySwitch
操作设备类的状态。
以上是类图中七个关键类的详细说明,这些类涵盖了电路核心逻辑、设备操作以及用户交互的主要功能。
设计心得
-
面向对象与继承的应用
本次设计采用了面向对象的思想,主要通过继承和多态实现设备类型的扩展与状态的管理。通过抽象类ElectricDevice
和具体子类(如SwitchDevice
、FanDevice
等),实现了不同设备的统一管理与个性化行为。继承使得代码结构更加清晰,同时避免了重复代码,方便后续扩展和维护。 -
简化模拟,专注核心功能
电气设备的模拟遵循了简化原则,没有考虑过于复杂的电气原理(如具体的电压、电流计算),主要模拟设备的开关、状态更新和电路连接等功能。这既保证了模拟的简洁性,也让系统易于理解和操作。虽然如此,设计时仍尽量保留了现实电路中串联与并联的基本概念。 -
命令设计与状态管理
设备的控制采用简短的命令(如#K
、#F
),这简化了用户输入的复杂度,使得操作更为直观。状态更新功能通过多态机制让不同类型的设备能够自行处理状态变化,避免了冗余的代码逻辑。 -
扩展性与可维护性
设计时考虑到未来的扩展需求,特别是在设备类型和电路模型上的扩展。通过合理的类结构和接口定义,新设备可以轻松地加入到系统中,且不会影响现有功能。设计时尽量保持了低耦合,高内聚的原则,确保系统具有较好的可维护性。 -
潜在的改进方向
虽然目前的设计已经能满足基本需求,但在错误处理和用户输入验证方面还有待完善。比如,在命令输入错误时,系统应提供友好的错误提示或容错机制。此外,如果未来需要更加精确的电气模拟(如功率计算、电流分配等),则需要进一步加强底层计算模块的设计。
PTA 家居强电电路模拟程序系列作业总结
代码反馈
1. 第一次题目
优点:
- 面向对象设计:很好地运用了抽象类和继承,将不同类型的电气设备(如开关、调速器、灯具等)抽象为不同的类,体现了良好的面向对象编程(OOP)原则。
- 模块化结构:电路构建、状态更新、命令处理和输出生成被划分到不同的类中,增强了代码的可读性和可维护性。
- 使用正则表达式:在解析输入时,使用了正则表达式来提取关键信息,这在处理复杂格式的输入时非常有效。
改进建议:
- 类命名与职责分离:
VoltagePropagator
类中的方法较为复杂,建议进一步拆分,将电压传播和电路计算逻辑分离,提升代码的可维护性。 - 异常处理:在解析输入和处理电路连接时,建议添加更多的异常处理机制,以防止由于输入格式错误导致程序崩溃。
- 消除冗余代码:例如,在
Main
类中存在两个main
方法,可能导致命名冲突。建议将不同的逻辑分离到不同的类或方法中,并确保每个文件只有一个public class
。 - 优化数据结构:在电路传播过程中,频繁使用
HashMap
和List
来存储和查找设备,影响性能。考虑使用更高效的数据结构,如TreeMap
以保持设备有序,或者引入图结构来更好地表示电路拓扑。
2. 第二次题目
优点:
- 扩展性强:通过
AllCircuit
类管理所有电路和设备,展示了良好的系统设计能力。 - 详细的设备类实现:每种设备(如
MySwitch
、MySPDT
、DividerControl
等)都有独立的状态更新逻辑,便于后续扩展和维护。 - 控制消息处理:
MyUserInput
类能够处理不同类型的控制消息,显示了对命令解析和设备控制的深入理解。
改进建议:
- 代码重复与简化:在多个设备类中,
updateState
方法存在重复的逻辑。可以考虑在基类中提供一些通用的状态更新逻辑,减少代码重复。 - 依赖注入:当前
AllCircuit
类中直接依赖具体的设备类,建议使用接口或抽象类进行依赖注入,提高代码的灵活性和可测试性。 - 优化电路计算逻辑:电路计算部分逻辑较为复杂,尤其是在处理串联与并联电路时。可以考虑引入更清晰的算法,如使用图遍历算法(DFS/BFS)来计算电压和电流分布。
- 增加注释与文档:尽管代码逻辑清晰,但适当的注释和文档能够帮助他人(或自己在未来)更快地理解代码的意图和工作原理。
总结心得反馈
1. 踩坑心得
-
正则表达式解析问题:发现由于输入格式的复杂性,初次使用的正则表达式无法正确解析,导致程序崩溃。通过优化正则表达式并逐步解析,成功解决了这一问题。
建议:在处理复杂输入时,可以考虑先进行预处理,如去除不必要的空格或特殊字符,分阶段解析不同部分,减少单一正则表达式的复杂度。
-
混合输入顺序管理:面对串联电路、并联电路和设备信息混杂的输入顺序,通过多轮解析方案解决了数据依赖的问题。
建议:在设计输入解析逻辑时,明确数据依赖关系,确保每一轮解析都能基于已解析的数据进行。这不仅提高了解析的准确性,也使代码逻辑更清晰。
-
混合电路的电压计算:指出初始实现未正确处理并联电路的等效电阻,导致电压分配错误。通过分段计算,实现了正确的电压分配。
建议:电路计算涉及复杂的物理规律,建议参考电路理论(如欧姆定律、基尔霍夫定律)进行设计和验证,确保计算逻辑的准确性。
-
输入解析与设备连接:通过引入
pinToDevice
映射,确保每个引脚唯一对应一个设备,避免了设备重复连接或漏连接的问题。建议:在设计设备连接逻辑时,确保每个连接点的唯一性和合法性,可以使用图结构来表示设备间的连接关系,便于后续的遍历和计算。
2. 改进建议
-
模块化设计:将输入解析、电路计算、设备状态更新和输出生成等逻辑分离到不同的模块中,降低耦合度。
扩展建议:进一步细化模块划分,如将命令处理独立为一个模块,使用设计模式(如观察者模式)来管理模块间的通信。
-
严格输入验证:增加输入验证逻辑,确保设备编号唯一、引脚连接合法,及时提示无效输入。
扩展建议:引入配置文件或标准化的输入格式,减少输入错误的可能性。同时,可以提供详细的错误信息,帮助用户快速定位问题。
-
优化数据结构:使用更高效的数据结构,如
TreeMap
保持设备有序,引入邻接表表示电路连接关系,便于拓扑遍历。扩展建议:考虑使用图论中的数据结构和算法,如邻接矩阵、邻接列表、拓扑排序等,来更高效地表示和处理电路拓扑。
作业中学到的内容
通过完成这两次家居强电电路模拟程序的作业,我在多个方面获得了深入的理解和宝贵的经验:
1. 面向对象编程(OOP)
- 封装:通过
ElectricDevice
及其子类,将设备的属性和行为进行封装,增强了代码的可维护性。 - 继承:利用继承机制,将共性功能抽象到基类,简化了子类的扩展。
- 多态:通过统一接口调用不同设备的状态更新方法,降低了代码耦合,提高了灵活性。
2. 电路物理特性的模拟
- 串联电路:理解电流相同、电压逐步分配的原理,并在代码中实现电流和电压的正确分配。
- 并联电路:掌握电压相等、分支电流按电阻反比分配的计算方法,计算等效电阻并分配电流。
- 等效电阻:应用串联和并联电阻公式,简化复杂电路的计算。
3. 数据结构应用
- Map:使用
HashMap
快速存储和查找设备信息。 - List:利用
List
有序存储串联电路中的设备连接关系。 - 邻接表:采用邻接表存储并联电路的连接关系,提高数据组织的灵活性。
4. 输入解析与错误处理
- 正则表达式:通过正则表达式逐层提取电路连接信息,简化解析过程。
- 分步处理:将输入解析分为多个步骤,逐步提取和处理不同类型的信息,降低复杂度。
- 异常处理:增加异常捕获机制,确保程序在遇到错误输入时能够稳定运行并提供有用的反馈。
5. 代码设计与模块化
- 模块化设计:将电路构建、状态更新、命令处理和输出生成分离到不同的类中,提升了代码的可读性和可维护性。
- 职责单一原则:每个类只负责特定的功能,减少了模块间的耦合,提高了系统的扩展性。
6. 调试与测试
- 调试技巧:通过逐步检查电压和电流的分配,确保每个设备的状态更新正确。
- 单元测试:为关键功能编写单元测试,确保各模块的功能正确。
- 集成测试:进行整体电路的集成测试,验证系统的稳定性和功能性。
最终反思
- 细节的重要性:每个细节—from正则表达式到电阻计算—都影响程序的稳定性和准确性。
- 前瞻性的设计:设计时需考虑系统的扩展性,以应对更复杂的需求。
- 调试的关键性:系统化的调试方法有助于快速定位和解决问题,提升开发效率。
对课程与作业的建议
- 增加案例分析:引入更多实际开发中的案例,帮助学生将理论与实践结合。
- 强化代码审查:通过同学间的互评,学习不同的实现思路和优化方法。
- 提供更高层次的挑战:设计更复杂的任务,如动态电路拓扑调整,提升学生的解决问题能力。
通过这两次作业,我不仅提升了编程技能,还深入理解了电路理论和面向对象编程的核心概念,为未来更复杂的项目打下了坚实的基础。