題目集4~6總結性Blog
------------------------------------------目錄------------------------------------------
一、 前言
二、 設計與分析
1. 題目集4的答題判題程式4
題目概述
類圖展示
設計思路
原始碼分析
效能評估
2. 題目集5的家居強電電路模擬程式1
題目概述
類圖展示
設計思路
原始碼分析
效能評估
3. 題目集6的家居強電電路模擬程式2
題目概述
類圖展示
設計思路
原始碼分析
效能評估
三、 採坑心得
1. 題目集4的答題判題程式4
2. 題目集5的家居強電電路模擬程式1
3. 題目集6的家居強電電路模擬程式2
四、 改進建議
1. 題目集4的答題判題程式4
2. 題目集5的家居強電電路模擬程式1
3. 題目集6的家居強電電路模擬程式2
五、 總結
-----------------------------------------------------------------------------------------
一、前言
本階段的三次作業,主要圍繞物件導向設計的繼承和多型、字串處理與解析、正規表示式、工廠模式和組合模式的設計模式等知識點進行展開。具體來說,題目集4聚焦於答題判題程式的設計與實現,涉及瞭如何透過多型和繼承進行題目型別擴充套件,並透過正規表示式解析輸入,題目集5和題目集6的電路模擬,主要考察瞭如何使用工廠模式、組合模式和繼承來設計電路裝置類,如何實現電路中的電壓計算和裝置連線,特別是串聯與並聯電路中電阻的處理。
題量適中,有挑戰性又不至於有挫敗感,每週一道大作業,一做就是一上午、一下午、一晚上,做出來就很有成就感。
難度方面,題目集4對題目擴充套件和判題邏輯提出了較高要求,題目集5和題目集6則在設計模式的應用上有較強的挑戰,尤其是在電路電壓計算和裝置儲存管理的抽象上。整個過程中,我逐步提升了對繼承和多型、設計模式和程式碼最佳化的理解,並透過對各個模組的獨立實現及協作,深化了對軟體工程的實際應用能力。
本文將對這三個題目集進行總結回顧,包括設計與分析、遇到的問題及解決策略,以及針對未來改進的一些建議。
二、設計與分析
題目集4的答題判題程式4
打亂順序輸入題目、試卷、學生、刪除題目資訊,題目型別細分為選擇題、填空題,允許多張試卷存在,可以以任意的先後順序輸入各類不同的資訊,輸出總分警示、答卷、判分、題目被刪除、引用錯誤、格式錯誤的資訊。
類的設計沿用了答題判題程式系列的大作業三,新增了繼承題目類的選擇題類和填空題類,再微調一下輸入輸出,就完成了。在此,真切感受到多型的好處。總之,主要包括題目、題庫、試卷、試卷集、學生資訊和答卷、答卷集等模組,採用物件導向的設計方式,透過類和物件封裝資料和行為,使用集合來儲存和管理相關資訊,並利用正規表示式解析輸入格式以確保正確性。可以擴充套件不同型別的題目,這裡擴充套件了選擇題和填空題,能夠計算試卷總分並檢查答案的正確性,同時提供判分資訊輸出。
原始碼分析
(1)順序圖:
- 大部分類功能與迭代前類似:
QuestionBank 類維護一個題庫,使用 Map 儲存題目,提供新增、刪除和查詢題目的功能。Test 類表示一張試卷,儲存試卷號和題號與分數的對映,能夠計算總分並檢查是否為100分。
TestBank 類則管理多個試卷,提供儲存和檢查試卷總分的功能。
Answer 類用於表示學生的答卷,包含試卷號、學號和答案,同時維護評分資訊。AnswerBank 儲存多個答卷,並提供檢查所有答卷的功能。
Student 類和 StudentTable 類用於管理學生資訊,包括學號和姓名的儲存與查詢。
輸入資訊的解析由 InfoBuild 類完成,它使用正規表示式確保輸入格式的正確性,並構建題庫、試卷和學生資訊。整體設計遵循物件導向原則,結構清晰,便於維護和擴充套件,但在異常處理方面還有改進的空間。
- 輸入處理的變化:用正規表示式來匹配各種格式,與之前相比,在本題需要增加選擇題和填空題的正規表示式,與問題的正規表示式類似,只是從N變成了Z和K。
//#N:1 #Q:1+1= #A:2
private final String questionPattern = "^#N:(\\d+) #Q:([^#]+) #A:(.*)";
//#Z:2 #Q:黨十八大報告提出要加強()建設。A 政務誠信 B 商務誠信 C社會誠信 D司法公信 #A:A B C D
private final String choicePattern = "^#Z:(\\d+) #Q:([^#]+) #A:(.*)";
//#K:2 #Q:古琴在古代被稱為: #A:瑤琴或七絃琴
private final String fillPattern = "^#K:(\\d+) #Q:([^#]+) #A:(.*)";
- 繼承的處理:choiceQuestion和fillQuestion對Question的繼承,重寫一下checkAnswer的單道題判斷的方法以適應不同題型的評分規則。判決結果原來採用boolean型別,由於除了true、false新增了partially correct,所以型別改為了String。全部題目的判斷,要遍歷題目列表,比較單題的答案,將結果存入判題列表。
- 總分的計算上也有所改變:根據判定結果決定在總分上加全部的分數,還是增加一半的分數,還是不加分數。
程式碼結構較為清晰,平均每類包含的方法數和平均每方法語句數都在合理範圍內。
分支語句佔比不高,程式碼流程控制不是特別複雜。
方法呼叫較多,由於模組化設計較好,功能分散到不同的方法中實現。
註釋覆蓋率為9.1%,略低,我應該增加更多的註釋來提高程式碼的可讀性。
最複雜方法的圈複雜度為10,屬於較高水平,應對該方法進行最佳化或分解,降低其複雜度。
平均複雜度為1.86,整體上程式碼的複雜度處於一個較好的水平。
InputInfo()方法圈複雜度最大,有10,因為新增了選擇、填空題型判斷的兩個分支。
題目集5的家居強電電路模擬程式1
實現電路模擬程式,電路中裝置有受控裝置和控制裝置。控制裝置分為開關、分檔和連續調速器等,根據輸入電壓得到輸出電壓,受控裝置是像燈和風扇,根據電壓差獲得不同的工作引數。
輸入帶裝置編號和引腳的連線資訊與#加裝置的控制資訊,按指定順序輸出工作引數。
在本題,電路裝置分為控制裝置和受控裝置,電路也可看作電路裝置,電路中又包含電路裝置,可以採用組合模式,受控裝置和控制裝置繼承電路裝置,電路類也積體電路裝置,同時電路類又聚合電路裝置。
由於本題只考慮串聯的形式,所以可以先寫一個抽象的電路類,用串聯電路繼承它,留有擴充套件的餘地。電路中除了開關可能出現多個,其他電路裝置均只出現一次,並且沒有給出電阻等電路引數,所以這部分處理比較粗略,受控裝置其中一側電壓一定為0,電壓差就是輸入電壓。
原始碼分析
- 順序圖
- ElectricalDevice:抽象基類,代表一個電氣裝置,定義了基本屬性如ID、輸入電壓、輸出電壓等,並提供了一些update等公共方法。控制裝置的update是根據輸入計算輸出電壓,受控裝置的update是根據電壓差計算工作引數。這也是多型的體現。
- ControlDevice 和 ControlledDevice:從ElectricalDevice繼承而來,分別用於表示控制裝置和受控裝置。ControlDevice負責調整其他裝置的狀態,而ControlledDevice則響應這些調整。
- 具體裝置類:包括Switch(開關)、MultiSpeedController(分檔調速器)、ContinuousSpeedController(連續調速器)、IncandescentLight(白熾燈)、FluorescentLight(日光燈)、CeilingFan(吊扇)。每個具體裝置類都實現了其特定的行為。
- Circuit:抽象基類,表示一個電路,定義了新增裝置、連線裝置、更新狀態等方法。
- SeriesCircuit 和 ParallelCircuit:從Circuit繼承而來,分別實現串聯電路和並聯電路的具體邏輯。本題沒有用到ParallelCircuit,但是考慮到之後可能的擴充套件,也設計了。
Lines: 432,檔案總共有432行程式碼。
Statements: 223,共有223條宣告語句。
Percent Branch Statements: 17.5%,分支語句佔所有宣告語句的比例約為17.5%,這表明程式碼中有一定比例的決策邏輯。
Method Call Statements: 83,方法呼叫語句共出現了83次。
Percent Lines with Comments: 8.8%,註釋行佔總行數的比例為8.8%,說明文件化程度較低。
Classes and Interfaces: 9,定義了9個類和介面。
Methods per Class: 5.67,平均每個類包含約5.67個方法。
Average Statements per Method: 4.24,每個方法平均含有4.24條宣告語句。
Line Number of Most Complex Method: 211,最複雜的函式開始於第211行。
Name of Most Complex Method: InfoBuild.buildInfo(),最複雜的方法是InfoBuild.buildInfo()。
Maximum Complexity: 5,最大圈複雜度為5。
Line Number of Deepest Block: 141,最深巢狀塊開始於第141行。
Maximum Block Depth: 4,巢狀深度最大值為4層。
Average Block Depth: 1.40,平均巢狀深度為1.40層。
Average Complexity: 1.71,平均複雜度為1.71。
具有較少的類和方法,同時分支語句和方法呼叫語句也保持在一個合理的水平。然而,註釋覆蓋率較低,這會影響程式碼的可維護性和理解難度。此外,雖然大多數方法的複雜度適中,但存在個別較複雜的方法,這需要進一步審查和重構以提高程式碼質量。
題目集6的家居強電電路模擬程式2
在第一次的基礎上,增加落地扇電路裝置,增加所有電路裝置的電阻的引數,增加一個並聯電路。輸入電路時,連線資訊按電路輸入。
第二次大作業在第一次大作業的基礎上增加了落地扇、並聯電路和電阻,落地扇好改,直接新增一個類就好啦,此外還要解決兩個問題,一個是輸入的資訊怎麼轉化和儲存,第二個是加入了電阻後電壓怎麼計算。
輸入的資訊要確保正確儲存,改變電壓可以透過電阻分壓法計算。
其中,電阻計算方法如下:
受控和控制裝置電阻都知道,串聯並聯電路也作為裝置,電阻可以算,演算法是串聯是電路所有電阻相加(把斷開的開關設成無窮大的電阻),並聯是電路電阻倒數和再取倒數,如果有一路電阻是0,那總電阻就是0。
在主函式呼叫總電路的計算電阻方法,會計算所有裝置的電阻求和,其中,受控控制裝置電阻直接返回,電路作為裝置計算並返回。這樣的類似遞迴的過程。
- 串聯電路計算電壓差的分壓方法方法:
- 並聯電路計算電壓差的分壓方法方法:
- 順序圖:
註釋比例:31.6%的註釋比例,表明程式碼有一定的可讀性和維護性。
分支語句比例:17.6%的分支語句比例相對較低,說明程式碼邏輯較為簡單。
方法呼叫比例:較高的方法呼叫(108次)意味著程式碼複用較好,但也需要關注是否存在過度依賴的情況。
最複雜方法:InfoBuild.buildInfo()方法具有最高的複雜度,在資訊處理的過程中,進行了許多對字串的判斷。我想,如果使用正規表示式應該可以減少這種不必要的分支判斷,之後可以持續改進。
最大巢狀深度:5層的巢狀深度較高,可能會增加理解難度,建議減少巢狀以提高可讀性。
平均複雜度:2.25的平均複雜度在合理範圍內,但仍然需要注意控制單個方法的複雜度。
總體來看,程式碼質量尚可,但仍有一些地方可以改進,如減少巢狀、最佳化複雜方法等。
三、採坑心得
題目集4的答題判題程式4
問題:在解析輸入時,初期我並未考慮到各種輸入格式的細微差異,導致一些合法輸入被錯誤地拒絕。
解決:為了解決這個問題,我花了更多時間仔細檢查每個正規表示式,確保它們能涵蓋所有可能的輸入情況,並透過單元測試驗證其正確性。
(2)問題:題目的結果判斷上,答案錯誤。
if(getStandard().equals( answer)) {
return "true";
}
解決:判斷時,不要把答案後的空格也加入判斷,要先用trim()函式去除空格,在新增的兩個題型子類中,沒有注意這一點。
(3)問題:本題中,要求亂序輸入,這意味著刪除題目的資訊可以出現在題目之前。
解決:對此的處理是,如果不存在這道題,先建立這道題,後續輸入這道題的資訊,再修改之前建立的這個題目物件。
//為了保證亂序也正常
if(questions.getOrDefault(qNum,null)==null) {//如果不存在這道題
Question q= new Question(qNum,"","");
q.setDelete(true);
saveQuestions(qNum,q);
}else{
questions.getOrDefault(qNum,null).setDelete(true);
}
- 在評分邏輯的實現中,我遇到了一些邊界情況,比如部分正確答案的評分。在初期的實現中,我沒有考慮到不同題型的評分規則,導致評分結果與預期不符。
解決:經過反覆測試和調整,我最終為每種題型設計了清晰的評分規則,並在程式碼中進行了詳細的註釋,以確保後續維護時能夠快速理解。
題目集5的家居強電電路模擬程式1
(1)問題:出現空指標異常。
解決:經過除錯,發現沒有對VCC進行特殊處理,出現了空指標。
(2)問題:混淆了Java和C++中取子字串的函式用法,導致擷取錯誤。
解決:Java的String類的substring() 方法和C++中std::string類的std::string::substr成員函式都有兩個引數,第一個引數是子字串開始的位置(基於 0 的索引),第二個引數Java中是不包括的結束索引,C++中是要提取的子字串的長度。如果不提供第二個引數,兩者都是擷取到末尾。
(3)問題:怎麼實現按照順序輸出的問題。
解決:建立裝置型別的列舉,每個裝置都返回裝置型別,在輸出前對所有裝置排序,之後再做輸出。
// 自定義比較器
class DeviceComparator implements Comparator<ElectricalDevice> {
@Override
public int compare(ElectricalDevice d1, ElectricalDevice d2) {
int typeComparison = d1.getType().ordinal() - d2.getType().ordinal();
if (typeComparison != 0) {
return typeComparison;
}
// 如果型別相同,則按編號排序
return d1.getId().compareTo(d2.getId());
}
}
題目集6的家居強電電路模擬程式2
- 問題:startsWith的引數不是正規表示式,就只是普通的字串。
解決:要注意。
- 問題:
短路的情況輸入,例如
#T1:[IN D2-1] [D2-2 OUT]
#T2:[IN D1-1] [D1-2 OUT]
#T3:[IN K3-1] [K3-2 OUT]
#M1:[T1 T2 T3]
#T4:[VCC K1-1] [K1-2 K5-1] [K5-2 M1-IN] [M1-OUT D3-1] [D3-2 GND]
#K1
#K5
#K3
End
輸出:D3輸出不正常。
問題:經過除錯,發現一些變數的值是NaN。接著除錯,發現是,根據邏輯計算電阻時,該為0處腦子糊塗程式碼中寫成了無窮大。
- 問題:很多答案錯誤。
解決:題目中要求不對工作電壓進行判斷,所以不應該限制落地扇的最高電壓。
- 問題:和調速器相關的測試點。
解決:調速器與VCC直連,有可能是連在與VCC直連的第一個串聯電路的第一個位置。要特別處理一下。還有調速器不止有連續,還有分檔,寫著寫著忘記了。
四、改進建議
題目集4的答題判題程式4
首先,增強輸入驗證機制是一個重要的改進方向。儘管當前使用正規表示式進行輸入解析,但仍可能存在邊界情況未被覆蓋的風險。在輸入處理過程中加入更為詳盡的驗證邏輯,確保使用者輸入的每個部分都符合預期格式,並提供清晰的錯誤提示,以便使用者能夠快速糾正錯誤。
其次,考慮到系統的擴充套件性,可以將題目型別進行抽象化處理。當前我的設計中,選擇題和填空題是透過繼承實現的,但隨著題型的增加,這種設計可能導致程式碼的複雜性增加。可以用工廠模式來處理不同型別的題目,使得系統能夠更靈活地支援未來可能新增的題型,同時降低程式碼耦合度。
此外,如果不是在PTA上寫的,而是一個實用的系統,還可以增加資料持久化功能,並增加圖形使用者介面。目前,所有資料都儲存在記憶體中。其實可以將題庫、試卷、學生資訊和答卷等資料儲存到資料庫中,也可以使用檔案儲存,確保資料的永續性和可恢復性。在使用者體驗方面,可以考慮增加圖形使用者介面。使用者可以透過點選和選擇的方式進行操作,提升整體的互動體驗。
題目集5的家居強電電路模擬程式1
(1)採用工廠模式處理輸入,根據裝置id建立新裝置,如下:
class DeviceFactory {
public static ElectricalDevice createDevice(String id) {
if (id.startsWith("K")) {
return new Switch(id);
} else if (id.startsWith("F")) {
return new MultiSpeedController(id);
} else if (id.startsWith("L")) {
return new ContinuousSpeedController(id);
} else if (id.startsWith("B")) {
return new IncandescentLight(id);
} else if (id.startsWith("R")) {
return new FluorescentLight(id);
} else if (id.startsWith("D")) {
return new CeilingFan(id);
} else {
throw new IllegalArgumentException("Unknown device ID: " + id);
}
}
}
(2)目前,裝置的比較器是在 printStatus 方法中建立的。應將比較器作為靜態常量定義,以避免每次呼叫 printStatus 時都重新建立比較器。
private static final Comparator<ElectricalDevice> DEVICE_COMPARATOR = new DeviceComparator();
public void printStatus() {
List<ElectricalDevice> deviceList = new ArrayList<>(deviceALL.values());
// 使用自定義比較器對裝置進行排序
Collections.sort(deviceList, DEVICE_COMPARATOR);
//省略了輸出的語句
}
(3)每個裝置的 update 方法都直接計算輸出電壓或工作引數。將一些公共的計算邏輯提取到基類中,以減少重複程式碼。
public abstract class ElectricalDevice {
// 省略了
public void update() {
differenceVoltage = inputVoltage - outputVoltage;
doUpdate();
}
protected abstract void doUpdate();
}
題目集6的家居強電電路模擬程式2
- 電路的計算邏輯比較複雜,可以透過策略模式將計算部分抽象出來,使用不同的計算策略來處理不同型別的電路。
// 電路計算策略介面
interface CircuitCalculationStrategy {
double computeVoltage(double inputVoltage, double[] resistances);
}
// 電路類
class Circuit {
private CircuitCalculationStrategy calculationStrategy;
public Circuit(CircuitCalculationStrategy calculationStrategy) {
this.calculationStrategy = calculationStrategy;
}
public double computeVoltage(double inputVoltage, double[] resistances) {
return calculationStrategy.computeVoltage(inputVoltage, resistances);
}
public void setCalculationStrategy(CircuitCalculationStrategy calculationStrategy) {
this.calculationStrategy = calculationStrategy;
}
}
- 使用 TreeSet(有序集合)來避免每次都進行排序。可以使用 TreeSet 來保證裝置按順序儲存。
private Set<ElectricalDevice> devices = new TreeSet<>(Comparator.comparing(ElectricalDevice::getId));
- 輸入處理的圈複雜度:可以用正規表示式處理以減少分支語句,減少圈複雜度。
- 程式碼結構和設計模式的最佳化:
裝置介面和類的設計:電器裝置和其子類(如控制裝置和受控裝置)設計上能進一步抽象。可以為 ElectricalDevice 新增一個 getVoltage() 方法來統一處理不同裝置的電壓獲取邏輯。這樣可以減少不同裝置類中重複的程式碼。
電路類的職責劃分:電路類目前負責太多的邏輯:既要管理裝置(addDevice、addConnection 等),又要處理電路計算(如 computeAllVoltage)。可以把電路中的計算和裝置管理分離開來。設計一個“電路計算器”類,負責根據裝置的狀態和電路的連線情況來計算電壓、電流等引數。
五、總結
透過本階段的題目,我不僅深化了對物件導向程式設計的理解,還在作業實踐中學到了許多寶貴經驗。
首先,題目集4讓我在實際操作中理解了多型、繼承和正規表示式的應用,並解決了輸入解析、題目刪除和答題判分等問題。這一過程中,我學到了如何有效處理複雜的輸入格式,以及如何最佳化判題規則來確保結果的準確性。
題目集5和題目集6則讓我使用工廠模式、組合模式、策略模式等設計模式,最佳化了電路裝置的管理與計算邏輯,尤其是在處理電路電阻計算和裝置連線管理的方面,得到了很好的鍛鍊。
然而,在實踐過程中,我也意識到一些需要進一步提升的地方。首先,正規表示式的使用上,我還需要進一步掌握其高效應用,尤其是在處理複雜輸入時的邊界情況。其次,對於設計模式的使用,儘管我已經有了一些初步的理解和實踐,但在更復雜的系統設計中,如何選擇最合適的設計模式,仍然需要更多的學習與探索。
老師可以在課程中增加更多關於設計模式和軟體架構的深入講解,並透過更多的實際案例來幫助我們更好地理解如何在不同場景下應用這些設計模式。此外,作業中如果能夠提供更多的測試用例和邊界情況,以便更好地驗證程式碼的魯棒性和效能,會對我們提高解決實際問題的能力大有幫助。
在課外的自學過程中,如何高效地進行問題分解、程式設計、圈複雜度降低等最佳化是我未來需要進一步加強的方向。在未來的學習中,我將繼續加強對設計模式和程式最佳化的理解,並透過實踐不斷提高自己程式設計的能力。