一、前言
这两次大作业中关于电路的分析与设计的开发让我对java这门语言的理解和应用又得到了提升,面向对象的编程对于解决实际生活中的问题拥有其他编程方式所没有的优势。在两次的类设计中我的考虑并不周全,忽视了
电路设备之间共性从而没怎么用到继承,这个问题在第一次串联电路的大作业中还说的过去,但是到了第二次并联电路的时候就变得十分要命了,我的并联电路和串联电路是两个完全独立的类,这让我在后续电路处理中
十分的棘手。总的来说,这两次大作业十分考验我类的设计和面向对象的编程思维。
二、第一次大作业
第一次大作业因为是首次接触到电路设计的编程题,尽管对串联电路的知识在初高中的时候已经学的相当多了,然而把知识转化为类设计和代码还是有相当大的挑战的。首先在一开始的类设计中,我先给题目中出现的设备
创建了一个Device类作为所有设备的父类。
点击查看代码
// 基础设备类
abstract class Device {
protected String id;
protected double inputVoltage = 0; // 输入电压
protected double outputVoltage = 0; // 输出电压
public Device(String id) {
this.id = id;
}
public abstract void updateState(); // 更新设备的状态
public String getId() {
return id;
}
public double getOutputVoltage() {
return outputVoltage;
}
public void setInputVoltage(double voltage) {
this.inputVoltage = voltage;
}
public void setoutputVoltage(double voltage) {
this.outputVoltage = voltage;
}
public abstract boolean isControlled(); // 是否被控制或连接
}
然后就是题目中给出的各个设备的类设计了,一开始我忽视了受控设备和控制设备的区别,直接让所有的设备都继承Device类,这个错误很快在后续的电压传递处理中让我意识到了,于是后续修改后,我将白炽灯、吊扇
等受控设备继承自受控设备类;开关、分档调速器等控制设备继承自控制设备类。受控设备类和控制设备类的差别在于受控设备多了一个markAsConnected()方法,意在受控设备在输入匹配的情况下调用该方法来标记设备
在电路中以用于后续的打印。
点击查看代码
// 受控设备类
abstract class ControlledDevice extends Device {
private boolean connected = false; // 是否被连接
public ControlledDevice(String id) {
super(id);
}
public void markAsConnected() {
connected = true;
}
@Override
public boolean isControlled() {
return connected;
}
}
在处理完各设备的类设计于继承关系后,我开始着手设计电路类。首先我声明了三个存储工具分别用于存储设备、设备两两关系、电路连接。
点击查看代码
, ``` private List电路类中connect方法是标记设备电路中连接关系的重要方法,其实现思路是:首先通过输入匹配到的id来获取对应devices 数组中的设备,在得到两个设备后判断id1是不是VCC和id2是不是GND,前者连接设备的输入电压
要置为220V、后者连接设备的输出电压要置为0V。在考虑过这两种情况后,才是两个普通设备的连接,在connections 哈希表中标记两个设备的连接。
点击查看代码
else {
connections.put(id1, id2);
// 标记设备为已连接
if (device1 instanceof ControlledDevice) {
((ControlledDevice) device1).markAsConnected();
}
if (device2 instanceof ControlledDevice) {
((ControlledDevice) device2).markAsConnected();
}
}
电路类中另一方法updateAllDevices方法用于更新所有设备的状态(包括输入输出电压、开关开闭、调档器档位等),以及根据连接关系传递电压。
首先更新所有设备的状态,即调用各个设备自身具备的updateState()方法。
点击查看代码
// 先更新控制设备的状态
for (Device device : devices) {
if (device instanceof ControlDevice) {
device.updateState();
}
}
// 再更新受控设备的状态
for (Device device : devices) {
if (device instanceof ControlledDevice) {
device.updateState();
}
}
点击查看代码
public void updateAllDevices() {
// 先更新控制设备的状态
for (Device device : devices) {
if (device instanceof ControlDevice) {
device.updateState();
}
}
// 根据连接关系传递电压
for (String id1 : connections.keySet()) {
String id2 = connections.get(id1);
Device device1 = deviceMap.get(id1);
Device device2 = deviceMap.get(id2);
if (device1 != null && device2 != null) {
device2.setInputVoltage(device1.getOutputVoltage());
}
}
// 再更新受控设备的状态
for (Device device : devices) {
if (device instanceof ControlledDevice) {
device.updateState();
}
}
}
编写完电路类Circuit后,我开始编写Main类。现在尚未完成的工作首先是从输入中匹配出所需的设备名、id号、连接关系、设备状态等信息,在这里我采用了正则表达式,这个方法可以高效的提取出所需的信息。
下面是第一次大作业Main函数的主要流程
导入必要的类:
Scanner:用于从控制台读取输入。
Circuit:电路类,用于管理电路中的设备和连接。
Switch、StepRegulator、ContinuousRegulator、CeilingFan、IncandescentLamp、FluorescentLamp:这些是电路中的设备类。
创建Circuit对象:
Circuit circuit = new Circuit();:创建一个电路对象,用于管理电路中的设备和连接。
创建设备实例:
创建了7个设备实例:两个开关(k1和k2)、两个调速器(一个步进调速器f1和一个连续调速器l1)、三个吊扇(d1、d2、d3)、一个白炽灯(b2)和一个荧光灯(r2)。
将设备添加到电路:
使用circuit.addDevice(device)方法将所有创建的设备添加到电路中。
定义正则表达式:
connectionPattern:用于解析设备之间的连接信息。
controlPattern:用于解析对设备的控制信息(如开关操作、调速操作)。
读取和解析输入:
使用Scanner对象从控制台读取输入,直到输入"end"。
对于每一行输入,使用connectionPattern和controlPattern正则表达式进行匹配。
如果匹配connectionPattern,则解析出两个设备名,并使用circuit.connect(device1, device2)方法将它们连接起来。
如果匹配controlPattern,则解析出设备ID、操作(增加或减少)和速度值(如果有),然后根据设备类型执行相应的操作。
处理异常:
如果在解析或执行操作时发生异常,会捕获异常并可以选择打印错误信息(在这段代码中被注释掉了)。
更新设备状态:
使用circuit.updateAllDevices()方法更新电路中所有设备的状态。
打印设备状态:
使用circuit.printAllStates()方法打印电路中所有设备的状态。
使用匹配[VCC K1-1]式连接信息的正则表达式:
Pattern connectionPattern = Pattern.compile("\[(\w+)-?(\d) (\w+)-?(\d)\]");
匹配#K1式控制信息的正则表达式:
Pattern controlPattern = Pattern.compile("#(\w+)([+-]?)([\d\.]+))?");
总体来说,我第一次大作业只拿到了60多分,即使题目中给出的十个输入输出样例都通过了,但是还是有很多监测点并没有通过。我觉得可能是我这种一开始就创建出题目中输入样例里所有可能会出现的设备的做法存在问题
,没能针对输入来创建设备和电路连接可能导致一些样例外的设备在输入后不能被匹配和添加到设备链表中,这就是我过不去后面很多监测点的原因,但是由于时间有限,最后我也未能完善这个问题,只能说还是得多花点时间
三、第二次大作业
第二次大作业相较于第一次大作业最重要的迭代就是多了并联电路设计,这意味着我需要在原来的电路类设计上增加一个并联电路子类,而且由于我第一次大作业中对于串联电路的类设计存在问题,导致我在这次作业中不知道怎样增加这个并联电路子类,以及并联电路类中首先肯定会包含多条串联电路,对于这些并联的串联来说,怎样去分配电压也是一个很困扰我的问题;而且就算解决了内部电压分配,考虑到并联电路也是总电路的一个组成设备,如何去处理并联电路在串联电路中的分压问题也增加了这次作业的任务量。
首先在第二次大作业新增了一个设备落地扇,它继承自受控设备类,且具备和普通吊扇不一样的输入输出属性;因此首先在第二次大作业中我增添了落地扇这个类,并在输入匹配中增加了A的匹配项以匹配落地扇。
同时由于此次作业中输入的信息的变化,在处理输入时首先得处理以#T+id开头的串联电路,接收其连接信息并将其添加进电路类的connection表中。这就意味着需要更改处理输入的正则表达式,事实上,由于我之前的电路类
设计以及Main处理,针对这次大作业的输入处理相当困难。首先在作业1中的连接信息都是 设备id+设备id的形式,而在这里有许多不是设备的例如IN、OUT,而且由于每次电路连接的信息输入时最后一条是总电路的信息,
总电路比一般串联电路多了一个并联电路的组成部分,这就导致它的匹配和一般电路也不一样。因此在处理输入信息时,我使用了多个正则表达式。
// 输入解析正则表达式
Pattern chuanlianPattern = Pattern.compile("#(\w+):\s((?:\[[^\]]+\]\s)+)");
Pattern connectionPattern = Pattern.compile("\[(\w+)-?(\d) (\w+)-?(\d)\]");
Pattern controlPattern = Pattern.compile("#(\w+)([+-]?)([\d\.]+))?");
Pattern blockPattern = Pattern.compile("\[(\w+)-\d+\s+([\w-]+)\]");
Pattern vccpattern = Pattern.compile("\[([\w]+)\s+([\w]+)-\d+\]");
Pattern MULTpattern = Pattern.compile("#(\w+):\[([\w]+)\s+([\w]+)\]");
其中chuanlianPattern、connectionPattern 、controlPattern是用来处理一般电路的输入,blockPattern、vccpattern是用来处理总电路中独立于并联电路的设备,MULTpattern则用来处理并联电路的输入。
但是尽管正确处理了输入信息,在我的类设计中还需要处理多条串联电路和并联电路的关系,如前言所说,我的并联电路类几乎是完全独立于串联电路类的存在,因此在电压分配和传递上对于总电路的分配几乎是不可能做到
,我首先尝试着创建3条电路,其中两条是并联电路中的串联子电路、还一条是总串联电路,我给他们创建并添加了输入输出可能会用上的所有设备,并且首先根据输入匹配的信息来在两条子串联电路中进行电压分配和电路连接,后面再添加从总电路中输入匹配到的设备并且予以输出。这里处理过后我通过了第一个输入输出样例,因为它的电路输入电压为0,所以尽管在总电路的电压处理我是错误的我也通过了这个样例。但是针对后面的两个样例,我必须得开始思考怎么去解决这个问题。首先我设计了一个并联电路类,并更改了原先电路中电压传递的逻辑:
并联电路设计:
点击查看代码
class ParallelCircuit extends Device {
private List<Circuit> seriesCircuits;
private double inputVoltage;
private double outputVoltage;
// 构造方法:初始化并联电路
public ParallelCircuit(double inputVoltage) {
this.seriesCircuits = new ArrayList<>();
this.inputVoltage = inputVoltage;
this.outputVoltage = 0;
}
// 重载构造方法:传入初始的串联电路列表
public ParallelCircuit(double inputVoltage, List<Circuit> circuits) {
this.seriesCircuits = new ArrayList<>(circuits);
this.inputVoltage = inputVoltage;
this.outputVoltage = 0;
setInputVoltage(inputVoltage);
}
// 添加串联电路
public void addSeriesCircuit(Circuit circuit) {
seriesCircuits.add(circuit);
}
// 设置并联电路的输入电压
public void setInputVoltage(double voltage) {
this.inputVoltage = voltage;
for (Circuit circuit : seriesCircuits) {
for (Device device : circuit.devices) {
if (device instanceof ControlledDevice || device instanceof ControlDevice) {
device.setInputVoltage(voltage);
}
}
}
}
// 计算并联电路的输出电压
public double calculateOutputVoltage() {
double minOutputVoltage = Double.MAX_VALUE;
boolean hasValidBranch = false;
for (Circuit circuit : seriesCircuits) {
circuit.updateAllDevices();
for (Device device : circuit.devices) {
if (device.isConnected()) {
hasValidBranch = true;
double branchOutput = device.getOutputVoltage();
minOutputVoltage = Math.min(minOutputVoltage, branchOutput);
}
}
}
this.outputVoltage = hasValidBranch ? minOutputVoltage : 0;
return this.outputVoltage;
}
}
电压传递逻辑(分串联电路传播和并联电路传播)修改:
点击查看代码
public void propagateVoltage() {
// 初始化所有设备的输入电压为 0
for (Device device : devices.values()) {
device.setInputVoltage(0);
}
// 处理串联电路
for (String circuitId : circuits.keySet()) {
String[] circuit = circuits.get(circuitId);
double voltage = 220; // 电源电压,从 VCC 开始
for (String connection : circuit) {
// 确保连接格式正确并包含两个引脚
String[] pins = connection.replace("[", "").replace("]", "").split(" ");
if (pins.length < 2) {
System.err.println("Invalid connection format: " + connection);
continue; // 跳过不合法的连接
}
String inputPin = pins[0];
String outputPin = pins[1];
if (inputPin.equals("VCC")) {
voltage = 220;
} else if (inputPin.equals("GND")) {
voltage = 0;
}
// 如果是并联电路的 IN 点,递归处理并联电路
if (parallelCircuits.containsKey(inputPin)) {
propagateParallelCircuit(inputPin, voltage);
} else {
// 传播到连接的设备
if (connections.containsKey(inputPin)) {
for (String pin : connections.get(inputPin)) {
String deviceId = pin.split("-")[0];
if (devices.containsKey(deviceId)) {
Device device = devices.get(deviceId);
device.setInputVoltage(voltage);
device.updateState();
voltage = device.getOutputVoltage();
}
}
}
}
}
}
}
// 并联电路传播
private void propagateParallelCircuit(String parallelId, double voltage) {
List<String> parallelCircuitsList = parallelCircuits.get(parallelId);
for (String circuitId : parallelCircuitsList) {
String[] circuit = circuits.get(circuitId);
double localVoltage = voltage;
for (String connection : circuit) {
// 确保连接格式正确并包含两个引脚
String[] pins = connection.replace("[", "").replace("]", "").split(" ");
if (pins.length < 2) {
System.err.println("Invalid connection format: " + connection);
continue; // 跳过不合法的连接
}
String inputPin = pins[0];
String outputPin = pins[1];
if (connections.containsKey(inputPin)) {
for (String pin : connections.get(inputPin)) {
String deviceId = pin.split("-")[0];
if (devices.containsKey(deviceId)) {
Device device = devices.get(deviceId);
device.setInputVoltage(localVoltage);
device.updateState();
localVoltage = device.getOutputVoltage();
}
}
}
}
}
}
细节分析:
parallelCircuits.get(parallelId) 获取了并联电路的所有子电路 ID。
遍历每个子电路 ID,按 circuits.get(circuitId) 获取子电路的连接数组。
对每个连接数组中的设备,使用电压 localVoltage 更新设备的输入电压,并获取新的输出电压。
存在的问题:
并联电路电压共享问题:
并联电路的核心特点是 每条分支共享相同的输入电压,但此代码中 localVoltage 被单独计算并更新,这会导致分支之间的电压状态不同。
并联的所有分支应该接收相同的输入电压,而不应随着设备更新而改变。
未处理设备输出汇总:
并联电路的分支在输出端需要汇总为一个电压值,但此代码未处理这种情况。
如果并联的多个分支设备有不同的输出电压,代码不会明确如何处理这些汇总。
缺少循环依赖检查:
如果输入数据中存在循环电路(某个设备的输出连接到其自身的输入),代码会陷入无限循环。
应加入循环检测机制。
未明确错误处理策略:
对于 circuits.get(circuitId) 返回 null 的情况,未做显式检查,可能会导致空指针异常。
对于解析不合法的连接,仅打印错误信息,但未通知调用方存在问题。
效率问题:
每次处理一个连接时,会重复访问 connections.get(inputPin) 和 devices.get(deviceId),导致性能下降,尤其在电路复杂时。
于是在进行优化后:
点击查看代码
// 保存各分支的输出电压
List<Double> branchVoltages = new ArrayList<>();
for (String circuitId : parallelCircuitsList) {
String[] circuit = circuits.get(circuitId);
if (circuit == null) {
System.err.println("Circuit not found: " + circuitId);
continue;
}
double branchVoltage = sharedVoltage;
for (String connection : circuit) {
// 确保连接格式正确
String[] pins = connection.replace("[", "").replace("]", "").split(" ");
if (pins.length < 2) {
System.err.println("Invalid connection format: " + connection);
continue;
}
String inputPin = pins[0];
String outputPin = pins[1];
// 缓存 connections 和 devices 的结果,减少重复访问
List<String> connectedPins = connections.get(inputPin);
if (connectedPins == null) continue;
for (String pin : connectedPins) {
String deviceId = pin.split("-")[0];
Device device = devices.get(deviceId);
if (device == null) continue;
// 更新设备输入电压并计算输出电压
device.setInputVoltage(branchVoltage);
device.updateState();
branchVoltage = device.getOutputVoltage();
}
}
// 保存每个分支的最终输出电压
branchVoltages.add(branchVoltage);
}
在处理这两个问题之后,由于时间问题,我并没能完善这并联电路和串联电路在总电路中的连接问题,导致后面两个样例不管怎样该都无法通过,最终导致这次大作业只通过了一个输入输出样例,在后面的优化过程中我必须得
完善根据输入信息创建设备以及处理并联电路和串联电路的兼容问题,如果不解决我就无法分配总电路的电压,这将导致我的大作业没有任何可行性。
四、总结
在这两次电路分析与设计的大作业中,我深刻体会到了面向对象编程的强大之处,尤其是在处理复杂问题时,良好的类设计和继承关系能够极大地简化问题。通过这两次实践,我不仅加深了对Java语言的理解,也提升了我的设计能力和编程技巧。
首先,我认识到了在类设计中考虑设备共性的重要性。在第一次大作业中,由于没有充分利用继承,导致了并联电路设计时的困难。我意识到,合理地使用继承可以减少代码的冗余,提高代码的可维护性和可扩展性。在第二次大作业中,我尝试通过创建并联电路子类来解决这一问题,但由于第一次大作业中的设计缺陷,这一过程并不顺利。
其次,我在处理电路连接和电压分配时遇到了挑战。在第一次大作业中,我通过设计电路类和相关方法,成功地实现了串联电路的模拟。然而,在第二次大作业中,面对并联电路的设计,我意识到需要重新思考电压分配和传递的逻辑。我尝试通过修改电路类和增加并联电路类来解决这些问题,但仍然存在一些逻辑上的问题和效率上的不足。
此外,我还学习到了正则表达式在解析输入信息中的重要作用。通过编写合适的正则表达式,我能够高效地从输入中提取出设备名、ID号、连接关系和设备状态等信息。然而,随着第二次大作业中输入信息的复杂性增加,我不得不编写更多的正则表达式来处理不同的输入情况,这也让我意识到了在设计输入解析逻辑时需要考虑的复杂性。
最后,我认识到了在实际编程中,理论知识和实践操作之间存在的差距。尽管我对电路的理论知识有一定的了解,但在将这些知识转化为代码时,我还是遇到了很多挑战。这两次大作业让我明白,理论知识是基础,但只有通过不断的实践和反思,才能真正掌握面向对象编程的精髓。
总的来说,这两次大作业是一次宝贵的学习经历。我不仅提升了自己的编程技能,也学会了如何在实际问题中应用面向对象编程的思想。虽然在实现过程中遇到了很多困难,但这些困难也让我更加明确了未来学习的方向。我将继续深入学习Java语言,提高我的编程能力,并在实际项目中不断实践和完善我的面向对象编程技巧。