第四次大作業到第六次大作業總結
一、前言
難度分析
-
第四題集:第四次大作業在前三次基礎上進行迭代,難度較大,得分較低。
-
第五題集:第五次大作業是新的一次命題,難度適中,得分較高。
-
第六題集:第六次大作業只有一題,雖然是第五次的迭代,但難度提升較大,更加貼近現實要求,得分不高。
三次實驗,第四次與前三次實驗相關,也是在前三次基礎上進行迭代,難度相比之前有較多的提高,主要問題還是在對輸入內容的讀取,程式碼始終無法對輸入部分進行準確的判斷,面對多選題時,無法準確判斷半對的情況,對於多張試卷的處理依舊存在很大的問題。在第五次作業中,因為是一個嶄新的開始,在系統建立中採用模組化設計,充分利用了物件導向的特性,透過繼承和多型性來實現不同裝置的具體行為。透過使用集合和對映結構,系統能夠快速訪問和更新裝置狀態,並確保裝置按編號排序。第六次作業總感覺難度提升有點大。
二、設計與分析
2.1 第四次題集
- 類設計:
Question 類:用於表示一個題目,包含題目ID、題幹、正確答案以及題目型別。提供了方法來檢查答案是否正確以及計算得分。
ExamPaper 類:用於表示一張試卷,包含試卷ID和題目及其對應分數的對映。提供了新增題目和計算總分的方法。
AnswerSheet 類:用於表示學生的答題紙,包含試卷ID、學生ID、學生姓名以及學生的答案。提供了新增答案的方法。
-
功能實現分析:
題目管理:程式碼透過解析以 #N:、#Z:、#K: 開頭的行來管理題目。每個題目包含題目ID、題幹、答案和題目型別(選擇題、填空題、簡答題)。使用 Map<Integer, Question> 儲存題目資訊,以題目ID為鍵,便於快速查詢。
試卷管理:解析以 #T: 開頭的行來管理試卷。每張試卷包含一個試卷ID和若干題目的ID及其對應分數。使用 Map<Integer, ExamPaper> 儲存試卷資訊,以試卷ID為鍵。檢查每張試卷的總分是否為100分,並在不符合時輸出警告。
學生資訊管理:解析以 #X: 開頭的行來管理學生資訊。每個學生包含一個學生ID和姓名。使用 Map<Integer, String> 儲存學生資訊,以學生ID為鍵。
答題記錄管理:解析以 #S: 開頭的行來管理學生的答題記錄。每個答題記錄包含試卷ID、學生ID和學生的答案。使用 List儲存答題記錄,方便後續的評分和排序。
題目刪除:解析以 #D: 開頭的行來記錄被刪除的題目ID。使用 Set儲存被刪除的題目ID,方便在評分時檢查題目是否有效。
評分與結果輸出:對每張答題紙進行評分,首先根據試卷ID和學生ID對答題紙進行排序。對每道題目,根據題目型別和學生的答案進行評分。
選擇題:答案必須與標準答案完全一致。
填空題:支援多個正確答案,學生答案只需匹配其中之一。
簡答題:支援部分正確的評分機制。
輸出每道題的結果,包括題幹、學生答案、正確性狀態,以及學生的總分。
錯誤處理:對於格式錯誤的輸入,輸出提示資訊。對於不存在的試卷、題目、學生,或被刪除的題目,提供相應的提示資訊。 -
PowerDesigner類圖:
2.2 第五次題集
- 類設計:
Control 類:這是一個基類,定義了輸入電壓和輸出電壓的基本屬性,以及更新輸出電壓的方法。其他裝置繼承自這個類,重寫 updateOutputVoltage 方法以實現特定裝置的功能。
SwitchControl 類:繼承自 Control,實現了一個簡單的開關控制。開關有兩個狀態:開啟和關閉,透過 toggleState 方法切換狀態。輸出電壓根據開關狀態更新。
FenDerive 類:繼承自 Control,實現了一個分檔調速器,可以在0到3檔之間調節。每檔對應不同的輸出電壓比例。
LianDeriver 類:繼承自 Control,實現了一個連續調速器,輸出電壓可以在0到輸入電壓之間連續變化。
Device 類:這是一個裝置基類,定義了電壓差的基本屬性。具體裝置如 Fan、IncandescentLamp 和 FluorescentLamp 繼承自這個類,並實現各自的功能,如計算速度或亮度。
DeviceManager 類:負責裝置的管理和連線。它維護了多個 TreeMap,分別儲存不同型別的裝置,並提供方法來處理裝置連線、控制命令以及輸出裝置狀態。
- 功能實現:
裝置連線管理:透過 processConnection 方法解析連線資訊,將裝置按順序連線在電路中。連線資訊被儲存在 connections 列表中。
裝置初始化:initializeDevices 方法遍歷連線列表,從 VCC 開始設定裝置的輸入電壓,並根據連線關係更新裝置的輸出電壓。
裝置控制:processControl 方法解析控制命令,更新裝置的狀態或引數(如開關狀態、調速器檔位或範圍),並更新連線裝置的輸出電壓。
狀態輸出:outputDeviceStatus 方法按裝置型別和編號順序輸出每個裝置的狀態資訊。
- 擴充套件性:
新增裝置型別:可以透過繼承 Control 或 Device 類輕鬆新增新型別的裝置,只需實現特定的功能邏輯。
新增控制命令:可以在 processControl 方法中新增新的命令解析邏輯,支援更多的裝置操作。
- PowerDesigner類圖:
2.2 第六次題集
- 類設計:
Control 類及其子類:Control 是一個抽象基類,定義了基本的輸入和輸出電壓屬性,以及更新輸出電壓的抽象方法。子類 SwitchControl、FenDerive 和 LianDeriver 實現了具體的控制邏輯。
SwitchControl 實現了開關控制,具有開關狀態的切換功能。
FenDerive 實現了分檔調速器,支援四檔調節。
LianDeriver 實現了連續調速器,輸出電壓可以在0到輸入電壓之間連續變化。
Device 類及其子類:Device 是一個抽象基類,定義了電壓差的基本屬性和獲取裝置狀態的抽象方法。子類 IncandescentLamp、FluorescentLamp、Fan 和 FloorFan 實現了具體的裝置功
能。子類透過實現 getStatus 方法來返回裝置的狀態,如亮度或轉速。
電路類:ConnectionPoint 表示電路中的連線點。SeriesCircuit 和 ParallelCircuit 分別表示串聯電路和並聯電路,支援複雜電路結構的表示。
DeviceManager 類:負責管理和控制所有裝置及其連線。使用多個 TreeMap 來儲存不同型別的裝置,按編號排序,便於管理和輸出。提供方法來處理電路連線、控制命令、裝置初始化和狀態輸出。
- 功能實現分析:
電路連線管理:processConnection 方法解析電路連線資訊,支援串聯和並聯電路的定義。processMainCircuit 方法識別主電路,用於後續的電壓計算。
裝置初始化:initializeDevices 方法根據電路連線關係,遞迴計算並設定裝置的輸入電壓。使用 traverseCircuit 方法遍歷電路,處理控制裝置和受控裝置的電壓更新。
控制命令處理:processControl 方法解析控制命令,支援對開關、分檔調速器和連續調速器的操作。applyControls 方法應用控制命令,並重新初始化裝置以反映命令的變化。
狀態輸出:outputDeviceStatus 方法按裝置型別和編號順序輸出每個裝置的狀態資訊。
- 設計特點:
物件導向設計:透過類的繼承和多型,實現了裝置和控制器的多樣性和通用性。基類 Control 和 Device 提供了基本的屬性和方法,具體子類實現各自的邏輯。
靈活的電路管理:支援複雜的串聯和並聯電路結構,能夠處理多種裝置的連線和控制。
使用集合類:TreeMap 用於儲存裝置,按鍵(裝置編號)自動排序,方便裝置的按序輸出。
- PowerDesigner類圖:
三、採坑心得
3.1 第四次題目集
1.原始碼提交過程中的問題
-
問題1:對於多選題無法準確判斷答案的對錯,即對於半對情況無法正確處理
-
部分原始碼分析:
public int calculateScore(String userAnswer, int maxScore) {
if (checkAnswer(userAnswer)) {
return maxScore;
} else if (type.equals("#Z:")) {
Set<String> correctAnswers = new HashSet<>(Arrays.asList(answer.split(" ")));
Set<String> userAnswers = new HashSet<>(Arrays.asList(userAnswer.split(" ")));
if (!correctAnswers.equals(userAnswers) && !Collections.disjoint(correctAnswers, userAnswers)) {
return maxScore / 2;
}
}
return 0;
}
public String getPartialCorrectStatus(String userAnswer) {
if (type.equals("#Z:")) {
Set<String> correctAnswers = new HashSet<>(Arrays.asList(answer.split(" ")));
Set<String> userAnswers = new HashSet<>(Arrays.asList(userAnswer.split(" ")));
if (correctAnswers.containsAll(userAnswers) && userAnswers.size() < correctAnswers.size()) {
return "partially correct";
}
}
return "false";
}
-
在 calculateScore 方法中:
-
首先檢查使用者的答案是否完全正確,如果是,則返回滿分。
-
對於多選題(type.equals("#Z:")),如果使用者答案與正確答案不完全相同,但存在交集(即使用者答案中包含部分正確答案),則返回一半的分數。
-
在 getPartialCorrectStatus 方法中:
-
對於多選題(type.equals("#Z:")),如果使用者的答案是正確答案的子集(即使用者選擇了部分正確答案,但未選擇全部),則返回 "partially correct"。否則,返回 "false"。
-
這兩個方法結合在一起處理了多選題部分正確答案的情況,透過計算部分得分和提供部分正確的狀態資訊。
3.2 第五次題目集
1.原始碼提交過程中的問題
-
問題1:對於已有測試案例全部透過,未找出具體錯誤原由。
-
部分原始碼分析:
public void initializeDevices() {
double currentVoltage = 220.0;
for (String[] connection : connections) {
String firstPin = connection[0];
String secondPin = connection[1];
// If firstPin is VCC, set inputVoltage to control device
if (firstPin.equals("VCC")) {
if (secondPin.startsWith("K")) {
int deviceNumber = Integer.parseInt(secondPin.substring(1, secondPin.indexOf('-')));
switches.putIfAbsent(deviceNumber, new SwitchControl());
switches.get(deviceNumber).setInputVoltage(currentVoltage);
}
// Similar logic for other device types (F, L, B, R, D)
}
// Logic for other pin types (K, F, L, etc.)
}
}
public void processControl(String line) {
if (line.startsWith("#K")) {
int deviceNumber = Integer.parseInt(line.substring(2));
SwitchControl sw = switches.get(deviceNumber);
if (sw != null) {
sw.toggleState();
double outputVoltage = sw.getOutputVoltage();
// Update all devices connected to this switch
}
}
// Similar logic for other control types (F, L)
}
-
在 initializeDevices 方法中:
-
功能:initializeDevices 方法負責根據連線資訊初始化各個裝置的輸入電壓。
-
邏輯:假設電源電壓為 220V(currentVoltage)。遍歷 connections 列表,解析每個連線。如果連線的第一個端子是 VCC,則將電壓傳遞給相應的控制裝置(如開關、分檔調速器、連續調速器等)。使用 putIfAbsent 方法確保每個裝置只被初始化一次。根據裝置型別(透過裝置識別符號的字首如 "K", "F", "L" 等)來建立並設定裝置的輸入電壓。
-
在 processControl方法中:
-
功能:processControl 方法處理輸入的控制命令,更新裝置狀態。
-
邏輯:檢查命令型別(開關、分檔調速器、連續調速器)。根據命令型別和編號獲取對應的裝置例項。執行裝置特定的狀態切換或調整操作(如切換開關狀態、調整調速器檔位)。呼叫裝置的 updateOutputVoltage 方法更新其輸出電壓。更新所有連線到該裝置的裝置的電壓差。
3.3 第六次題目集
1.原始碼提交過程中的問題
- 問題1:在電壓更新邏輯上有問題
- 部分原始碼分析:
private void traverseCircuit(String circuitId, double inputVoltage) {
if (circuitId.startsWith("T")) {
SeriesCircuit sc = seriesCircuits.get(circuitId);
if (sc == null) return;
double currentVoltage = inputVoltage;
for (ConnectionPoint cp : sc.connections) {
String deviceId = cp.deviceId;
String pinName = cp.pin;
String deviceType = getDeviceType(deviceId);
int deviceNumber = getDeviceNumber(deviceId);
// 處理特殊引腳
if (deviceId.equals("VCC") || deviceId.equals("GND") || deviceId.equals("IN") || deviceId.equals("OUT")) {
continue;
}
if (deviceNumber == -1) {
continue;
}
// 處理控制裝置
if (deviceType.equals("K")) {
switches.putIfAbsent(deviceNumber, new SwitchControl());
SwitchControl sw = switches.get(deviceNumber);
sw.setInputVoltage(currentVoltage);
currentVoltage = sw.getOutputVoltage();
} else if (deviceType.equals("F")) {
fenDerives.putIfAbsent(deviceNumber, new FenDerive());
FenDerive fen = fenDerives.get(deviceNumber);
fen.setInputVoltage(currentVoltage);
currentVoltage = fen.getOutputVoltage();
} else if (deviceType.equals("L")) {
lianDerivers.putIfAbsent(deviceNumber, new LianDeriver());
LianDeriver lian = lianDerivers.get(deviceNumber);
lian.setInputVoltage(currentVoltage);
currentVoltage = lian.getOutputVoltage();
} else {
// 處理受控裝置
double deviceVoltage = currentVoltage;
switch (deviceType) {
case "B":
incandescentLamps.putIfAbsent(deviceNumber, new IncandescentLamp());
IncandescentLamp lampB = incandescentLamps.get(deviceNumber);
lampB.setVoltageDifference(deviceVoltage);
break;
case "R":
fluorescentLamps.putIfAbsent(deviceNumber, new FluorescentLamp());
FluorescentLamp lampR = fluorescentLamps.get(deviceNumber);
lampR.setVoltageDifference(deviceVoltage);
break;
case "D":
fans.putIfAbsent(deviceNumber, new Fan());
Fan fan = fans.get(deviceNumber);
fan.setVoltageDifference(deviceVoltage);
break;
case "A":
floorFans.putIfAbsent(deviceNumber, new FloorFan());
FloorFan floorFan = floorFans.get(deviceNumber);
floorFan.setVoltageDifference(deviceVoltage);
break;
default:
System.err.println("未知裝置型別: " + deviceType);
}
}
// 檢查是否是並聯電路的入口
if (pinName.equals("IN")) {
String parallelId = deviceId;
ParallelCircuit pc = parallelCircuits.get(parallelId);
if (pc != null) {
for (SeriesCircuit subSc : pc.seriesCircuits) {
traverseCircuit(getCircuitId(subSc), currentVoltage);
}
}
}
}
} else if (circuitId.startsWith("M")) {
ParallelCircuit pc = parallelCircuits.get(circuitId);
if (pc == null) return;
for (SeriesCircuit sc : pc.seriesCircuits) {
traverseCircuit(getCircuitId(sc), inputVoltage);
}
}
}
-
在 traverseCircuit方法中:
-
電路型別判斷:
該方法首先根據 circuitId 判斷當前處理的是串聯電路(T 開頭)還是並聯電路(M 開頭)。
串聯電路處理:
對於串聯電路,方法遍歷其中的每一個 ConnectionPoint。
對於每個裝置,根據其型別(如開關、調速器、燈具等)設定輸入電壓並計算輸出電壓。
如果裝置是控制裝置(如開關或調速器),則使用其輸出電壓作為下一個裝置的輸入電壓。
並聯電路處理:
對於並聯電路,方法遍歷其中的每一個串聯電路。
每條串聯電路都接收到相同的輸入電壓。
裝置狀態更新:
對於受控裝置(如燈具和風扇),直接設定其電壓差,而不改變 currentVoltage。
遞迴呼叫:
當遇到並聯電路的入口時(IN 引腳),遞迴呼叫 traverseCircuit 方法處理並聯電路的每一條串聯支路。
四、改進建議
4.1 對程式設計題目的改進意見
輸入驗證和錯誤處理:
增加對輸入的更多驗證,以確保輸入的格式和內容符合預期。例如,驗證學生ID和題目ID是否存在,確保分數是有效的整數等。處理異常情況,例如輸入格式錯誤時丟擲異常並提供有意義的錯誤資訊。程式碼結構和可讀性:將主方法中的邏輯拆分為多個小方法,每個方法負責一個特定的功能(如處理題目輸入、處理試卷輸入、處理學生輸入等)。使用有意義的變數和方法名,以提高程式碼的可讀性。使用集合框架的最佳實踐:對於 questionMap 和 examPaperMap,可以考慮使用 ConcurrentHashMap 以提高執行緒安全性(如果有多執行緒需求)。使用 LinkedHashMap 代替 HashMap,如果需要維護插入順序。
4.2 對程式設計題目的改進意見
物件導向設計
多型性:考慮為 Device 類新增一個抽象方法 updateStatus,然後在每個子類中實現,以減少在 DeviceManager 中的重複程式碼。介面或抽象類:為控制裝置(如 SwitchControl, FenDerive, LianDeriver)建立一個介面或抽象類,以統一處理電壓更新邏輯。
設計模式上:
1.觀察者模式:如果裝置狀態變化需要通知其他元件,可以考慮使用觀察者模式來實現這種通知機制,但對於這些模式還是不勝瞭解。
2.命令模式:對於控制命令的處理,可以使用命令模式,將每個控制命令封裝為一個物件,以提高程式碼的靈活性。
4.3 對程式設計題目的改進意見
並聯電路電壓處理:
1.確保在並聯電路中,每個支路都能接收到相同的輸入電壓。在當前實現中,可能由於遞迴邏輯或電壓傳遞不當,導致並聯電路中的裝置沒有正確分配到電壓。
在處理並聯電路時,可以記錄並輸出每個支路接收到的電壓,以確保邏輯正確。
電壓傳遞邏輯:
2.在 traverseCircuit 方法中,確保每個裝置在設定輸入電壓後,正確更新其輸出電壓,並將其作為下一個裝置的輸入電壓。
檢查 updateOutputVoltage 方法的實現,確保每個裝置根據其當前狀態正確計算輸出電壓。
3.在 applyControls 方法中,確保在應用控制命令後,所有受控裝置(如燈具和風扇)都能根據新的電壓差更新其狀態。
可以在每次狀態更新後輸出裝置的電壓差和狀態,以便除錯。
五、總結
對本階段三次題目集的總結
對於第五次題目,程式碼模擬了一個裝置管理系統,用於管理各種電氣裝置及其控制機制。該系統設計用於處理不同型別的裝置,如開關、調速器、白熾燈、日光燈和風扇。使用了工廠模式來建立和管理不同型別的裝置和控制器根據輸入的裝置型別和編號,動態地建立例項並儲存在相應的集合中。也有用到策略模式,使得各種裝置透過繼承Device類,並實現自己的邏輯來計算速度或亮度。這些裝置類可以看作是不同的策略,透過多型性在執行時選擇合適的策略來執行。
對於第六次題目,程式碼是一個裝置管理系統,能夠處理各種裝置(如開關、燈具、風扇等)之間的連線和控制。雖然功能上基本實現了需求,但在處理複雜電路時,特別是並聯電路的電壓分配和裝置狀態更新方面,但還是存在一些問題。並聯電路中的電壓分配可能不準確,導致裝置狀態(如風扇轉速)不符合預期。在應用控制命令後,裝置狀態更新可能沒有正確反映電路的變化。在這次程式碼中雖然沒有直接實現工廠模式,但程式碼中有類似工廠模式的元素。例如,在 traverseCircuit 方法中,根據裝置型別建立不同的裝置物件(如 SwitchControl、FenDerive、LianDeriver 等),可以進一步抽象為工廠方法。雖然程式碼中沒有顯式使用某些設計模式,但透過這些程式設計方法和設計原則,程式碼實現了較好的模組化和靈活性。透過進一步重構,可以更清晰地實現一些設計模式,如工廠模式或觀察者模式,以增強程式碼的可維護性和可擴充套件性。