一、前言
概括:經過了三次java的大作業的練習,也算是深入java了。這三次大作業的難度是層層遞進的,雖然一次比一次難,但是每一次大作業都是基於前面大作業的知識點。所以每一次大作業認真完成,並認真總結知識點,多花點時間,大作業還是勉強可以完成。
1.知識點:
大作業4:大作業四還是答題判題程式的迭代,但增加了繼承的使用,程式中SingleChoiceQuestion、MultipleChoiceQuestion、FillInTheBlanksQuestion
都是Question的子類,繼承了父類的屬性和方法。相比之前的答題判題程式,使用了多種資料結構來組織資料,如使用Map來儲存題庫、試卷資訊等。且增加了異常處理使用Optional類來避免空指標異常,例如在getScoreForQuestion方法中,程式碼相比之前更加健壯了。這也是此次答題判題程式中改進的地方。
大作業5:大作業五,使用到了介面,重寫方法,List 介面,Map 介面。大部分都是有關Java集合框架,透過這些介面儲存和操作集合資料。
大作業6:大作業六進一步考察了抽象類和介面的使用,設計展示了一個智慧家居系統的基本框架,透過定義抽象類和具體裝置類,實現了裝置的多型性和可擴充套件性。PinandDevice類負責處理裝置連線和控制命令,透過Map資料結構管理裝置和連線關係。進一步體現了物件導向的思維。
2.題量與難度
這三次大作業題量都差不多,由於第四次大作業是答題判題程式的最後一次迭代,難度下降了。第五,六次大作業難度是一次比一次大。不過每一次大作業都是基於以前大作業的知識點,進一步增加新的知識點。使用了更多的類及其方法,要從網上查詢資料,瞭解相關類的使用。這些大作業無非都以物件和類的形式存在,使用到了物件導向的三個特性:封裝、繼承、多型
。所有透過寫這些大作業,java設計與程式設計能力可以得到很大提升,每一次多要認真對待。
二、設計與分析
1.第四次大作業
概括:由於是答題判題程式的最後一次迭代,難度相較於之前有所下降。
第一題
校園角色類設計:這次設計要用到繼承,角色Role分兩類:學生Student和僱員Employee;僱員又分為教員Faculty和職員Staff。設計子類Faculty和Staff繼承僱員Employee,透過使用關鍵字super呼叫父類構造方法來建立子類例項
第二題
設計一個學生類和它的一個子類——本科生類:也是與第一題類似,學生類作為父類,本科生類作為子類繼承學生類。在測試類Main的main( )
方法中,呼叫Student類的帶引數的構造方法建立物件object1,調Undergraduate類的帶引數的構造方法建立物件object2,然後分別呼叫它們的show( )
方法。
第三題
此題是答題判題程式的最後一次迭代,也是終於來到了答題判題程式的終點(屬於是擺脫這個噩夢了/(ㄒoㄒ)/~~)。本次迭代新增了多選題和填空題的判斷,之前都是單選題。此次要考慮考慮多個同學有多張不同試卷的答卷的情況,之前只是考慮一個學生不同答卷情況。最重要的一點是,此次要用到繼承,繼承這個概念在前幾作大作業都沒有使用到,這次新增了這個要求。
類圖
順序圖
下面是程式碼的分析以及一些解釋和心得:
QuestionType列舉
使用列舉,用於表示不同型別的題目,如單選、多選和填空。這是一個簡單但非常有用的設計,因為它允許在程式碼中以一種清晰且型別安全的方式引用這些型別。
//列舉,表示題目種類
enum QuestionType {
SINGLE_CHOICE,
MULTIPLE_CHOICE,
FILL_IN_THE_BLANKS
}
Question 類
- 屬性:
id:問題的唯一識別符號。
content:問題的內容或描述。
answer:問題的標準答案。
type:問題的型別,使用 QuestionType 列舉表示(儘管列舉的定義未在程式碼中給出,但可以推斷其存在)。 - 構造方法:接收問題的 id、content、answer 和 type 作為引數,並初始化這些屬性。
方法:
getId()、getContent()、getAnswer() 和 getType():分別用於獲取問題的 id、content、answer 和 type。
isPartiallyCorrect(String givenAnswer):一個私有方法,用於判斷填空題的部分正確性。它透過比較標準答案和給定答案(去除空格並轉換為小寫)的包含關係來實現。
checkAnswer(String givenAnswer):一個公開方法,根據問題的型別(單選題、多選題或填空題)來判斷給定答案的正確性。它返回一個 Serializable 物件(這裡實際返回的是 Boolean 或 String),表示答案是否完全正確、部分正確或完全錯誤。 - 子類
SingleChoiceQuestion:單選題類,繼承自 Question 類。其構造方法直接呼叫父類的構造方法,沒有新增額外的功能或屬性。
MultipleChoiceQuestion:多選題類,同樣繼承自 Question 類。構造方法也是直接呼叫父類的構造方法。
FillInTheBlanksQuestion:填空題類,繼承自 Question 類。構造方法直接呼叫父類的構造方法。
public Serializable checkAnswer(String givenAnswer)
是 Question 類中的一個重要方法,用於檢查使用者提供的答案是否正確。這個方法根據不同的題型(單選題、多選題、填空題)有不同的檢查邏輯。
點選檢視程式碼
private boolean isPartiallyCorrect(String givenAnswer) {
// 刪除標準答案中的空格,以便於比較
String trimmedAnswer = answer.replace(" ", "").toLowerCase();
String trimmedGivenAnswer = givenAnswer.replace(" ", "").toLowerCase();
// 檢查部分正確性
return trimmedGivenAnswer.contains(trimmedAnswer)
|| trimmedAnswer.contains(trimmedGivenAnswer)
&& !trimmedGivenAnswer.isEmpty()
&& !trimmedAnswer.isEmpty();
}
public Serializable checkAnswer(String givenAnswer) {
switch (type) {
case SINGLE_CHOICE:
return answer.equals(givenAnswer.trim());
case MULTIPLE_CHOICE:
var stdAns = new HashSet<>(Arrays.asList(answer.split(" ")));
var ans = new HashSet<>(Arrays.asList(givenAnswer.trim().split(" ")));
// 完全正確:答案包含所有正確答案
if (stdAns.containsAll(ans) && ans.size() == stdAns.size()) {
return true;
}
// 部分正確:答案包含部分正確答案且不含錯誤答案
if (stdAns.containsAll(ans) && ans.size() < stdAns.size()) {
return "partially correct";
}
// 完全錯誤:答案包含錯誤答案或未作答
return false;
case FILL_IN_THE_BLANKS:
// 完全正確:答案與標準答案內容完全匹配
if (answer.equalsIgnoreCase(givenAnswer.trim())) {
return true;
}
// 部分正確:答案包含部分正確答案且不含錯誤字元
if (isPartiallyCorrect(givenAnswer.trim())) {
return "partially correct";
}
// 完全錯誤:答案包含一個錯誤字元或完全沒有答案
return false;
default:
throw new IllegalStateException("Unexpected value: " + type);
}
}
對方法
checkAnswer
中每個部分的詳細解釋:
單選題(SINGLE_CHOICE):
使用 equals 方法比較標準答案和給定答案(去除空格後)。如果完全匹配,返回 true;否則返回 false。
多選題(MULTIPLE_CHOICE):
將標準答案和給定答案都分割成單詞,並儲存在 HashSet 中,以便進行集合操作。
完全正確:如果給定答案集合包含標準答案集合中的所有元素,並且兩者的大小相同,則返回 true。
部分正確:如果給定答案集合包含標準答案集合中的所有元素,但給定答案的大小小於標準答案的大小(意味著使用者沒有選擇所有正確答案,但也沒有選擇任何錯誤答案),則返回 "partially correct"。
完全錯誤:在其他情況下(即給定答案包含錯誤答案或未作答),返回 false。
填空題(FILL_IN_THE_BLANKS):
使用 equalsIgnoreCase 方法比較標準答案和給定答案(去除空格後)。如果完全匹配,返回 true。
使用 isPartiallyCorrect 方法檢查部分正確性。如果給定答案包含標準答案中的部分字元(忽略空格和大小寫),則返回 "partially correct"。
在其他情況下(即給定答案與標準答案不匹配,並且也不是部分正確),返回 false。
預設情況:
如果問題的型別不是預期的值之一,則丟擲 IllegalStateException。
QuestionBank 類
- 功能:
儲存一系列問題,並透過問題ID進行索引。
支援新增、刪除、查詢問題等功能。 - 方法:
成員變數 | 使用 LinkedHashMap 來儲存問題,以保持插入順序 |
---|---|
addQuestion(Question question) | 向題庫中新增一個問題 |
getQuestionById(int id) | 根據問題ID獲取問題 |
containsQuestion(int id) | 檢查題庫中是否存在某個問題ID |
deleteQuestion(int id) | 刪除某個問題 |
Map 介面
使用了 Map 介面提供的方法,如 put、get、containsKey 和 remove,來操作問題物件。
泛型
Map<Integer, Question> 使用了泛型來指定鍵和值的型別,這有助於型別安全。
集合框架
使用了 Java 集合框架中的 Map 介面和 LinkedHashMap 實現類。
Paper 類
- 功能:
表示一份試卷,包含多個問題及其對應的分數。
支援新增、查詢試卷資訊,計算試卷總分等功能。 - 方法:
成員變數:使用 HashMap 來儲存試卷資訊,其中鍵是試卷ID,值是一個 Map,該 Map 的鍵是問題ID,值是分數。 |
---|
addPaper(int paperId, int questionId, int score):向試卷中新增一個問題及其分數。 |
getPaper(int paperId):根據試卷ID獲取試卷資訊。 |
getTotalScore(int paperId):計算給定試卷的總分。 |
containsPaper(int paperId):檢查是否存在某個試卷ID。 |
getAllQuestionIds(int paperId):獲取試卷上的所有題目ID。 |
getScoreForQuestion(int paperId, int questionId):根據試卷ID和題目ID獲取相應的分數。 |
輸入格式:試卷資訊為獨行輸入,一行為一張試卷,多張卷可分多行輸入資料。
為了使每一張試卷id對映唯一 一張試卷,故使用Map<Integer, Map<Integer, Integer>>
使用巢狀對映(nested maps)的方式來組織資料的結構,外層 Map 的鍵是試卷的 ID (paperId),值是一個內層的 Map,這個內層 Map 的鍵是問題的 ID (questionId),值是問題的分數 (score)。這樣可透過試卷id可獲取該試卷上的所有題目的分數。
Studentinformation 類
- 功能:
儲存學生的學號及其對應的姓名。
支援新增、查詢學生資訊等功能。 - 方法:
成員變數:使用 HashMap 來儲存學生的學號及其姓名。 |
---|
addStudent(String id, String name):向學生資訊表中新增一條記錄。 |
getName(String id):根據學號獲取學生的姓名。 |
containsStudent(String id):檢查學生資訊表中是否存在某個學號 |
AnswerPaper 類
- 功能:
儲存學生的答卷資訊,包括試卷ID、學生學號及其回答的問題列表。
支援新增、查詢學生答卷資訊等功能。 - 方法:
成員變數:使用 HashMap 來儲存學生的答卷資訊,其中鍵是試卷ID,值是一個 Map,該 Map 的鍵是學生的學號,值是一個問題答案列表。 |
---|
addAnswerSheet(int paperId, String studentId, List |
getAnswerSheet(int paperId):根據試卷ID獲取所有學生的答卷記錄。 |
巢狀類:Answer 用於表示單個答案,包含題目序號和答案內容。 |
final Map<Integer, Map<String, List<Answer>>> answerSheets = new HashMap<>();
這個成員變數是一個三層巢狀的 Map 結構:
第一層 Map 的鍵是 int 型別,表示試卷的 ID (paperId)。
第二層 Map 的鍵是 String 型別,表示學生的 ID (studentId)。
第二層 Map 的值是 List
DeletionQuestion 類
- 功能:
記錄已被刪除的問題ID。
支援標記刪除問題和檢查問題是否已被刪除。 - 方法:
成員變數:使用 HashSet 來儲存已刪除的問題ID。 |
---|
markDeleted(int questionId):標記某個問題已被刪除。 |
isDeleted(int questionId):檢查某個問題是否已被刪除。 |
private final Set<Integer> deletedQuestions = new HashSet<>();
這個成員變數是一個 Set,用於儲存已被刪除的問題 ID。
使用 HashSet 作為實現類,這是因為 HashSet 提供了平均時間複雜度為 O(1) 的快速查詢、新增和刪除操作。
deletedQuestions 是 final 型別的,這意味著集合本身不能被重新賦值,但集合內的元素是可以被新增和刪除的。
輸入處理 (Input 類)
- 功能:
處理從標準輸入讀取的資料行,並根據行的字首呼叫相應的方法來處理這些行。 - 方法:
建構函式:接收題庫、試卷、學生資訊、答卷以及刪除題目的例項。 |
---|
validateInputFormat_1(String line):驗證題目輸入行的格式是否正確。 |
validateInputFormat_2(String line):驗證試卷輸入行的格式是否正確。 |
handleInput(String line):根據輸入行的字首處理不同的輸入型別。 |
- 輸入行處理邏輯:
題目輸入:以#N:開頭,格式為#N:問題ID #Q:問題內容 #A:答案。 |
試卷輸入:以#T:開頭,格式為#T:試卷ID 問題ID-分數 問題ID-分數 ...,並檢查試卷總分是否為100。 |
學生資訊輸入:以#X:開頭,格式為#X:學號-姓名 學號-姓名 ...。 |
答卷輸入:以#S:開頭,格式為#S:試卷ID 學號 #A:問題序號-答案內容 #A:問題序號-答案內容 ...。 |
刪除題目輸入:以#D:N-開頭,格式為#D:N-問題ID。 |
輸入類中正規表示式部分的詳細解析
使用正則表達是為了驗證輸入行(題目資訊,試卷資訊行)的格式是否符合預期的標準,符合預期則返回true,不符合則返回false且輸入的該行無效丟棄,不做輸入處理。
validateInputFormat_1
用於驗證題目輸入行的格式。
確保輸入行以 #N: 開頭,並且包含題目 ID、問題內容(包含數字加法運算)和答案。
validateInputFormat_2
用於驗證試卷輸入行的格式。
確保輸入行以 #T: 開頭,並且包含試卷 ID 和一系列問題及其分數。
點選檢視程式碼
public static boolean validateInputFormat_1(String line) {
// 正規表示式用來驗證輸入行的格式
String regex = "^#N:\\d+(\\s+#Q:\\d+\\+\\d+=\\s+#A:\\d+)$";
return line.matches(regex);
}
格式說明:
^#N::行開始位置 ^ 後跟 #N:,表示該行為題目資訊。
\d+:一個或多個數字,表示題目 ID。
(\s+#Q:\d+\+\d+=\s+#A:\d+):空格 \s+,後跟 #Q: 和一個或多個數字,表示問題內容中包含數字加法運算,然後是 =,接著是空格 \s+ 和 #A:,最後是一個或多個數字,表示答案。
$:行結束位置。
點選檢視程式碼
public static boolean validateInputFormat_2(String line) {
// 正規表示式用來驗證輸入行的格式
String regex = "^#T:\\d+(\\s+\\d+-\\d+)+$";
return line.matches(regex);
}
格式說明:
^#T::行開始位置 ^ 後跟 #T:,表示該行為試卷資訊。
\d+:一個或多個數字,表示試卷 ID。
(\s+\d+-\d+)+:空格 \s+,後跟一個或多個數字 - 一個或多個數字,表示問題及其分數,這樣的組合出現一次或多次 +。
$:行結束位置。
輸出處理 (Output 類)
- 功能:
根據已處理的資料輸出考試結果。 - 方法:
建構函式:接收題庫、試卷、學生資訊、答卷以及刪除題目的例項。 |
processOutput():遍歷所有答卷記錄,輸出每個學生的得分情況。 |
這段程式碼是整段程式碼中最核心的部分,用於處理輸出邏輯,包括根據學生的答卷情況計算成績,並輸出最終的成績資訊。
processOutput()
方法處理過程:
1.遍歷答卷:
- 遍歷 answerPaper 中的所有答卷記錄。
- 每個記錄是一個鍵值對,鍵是試卷 ID (paperId),值是一個對映關係,鍵是學生 ID (studentId),值是該學生的答案列表。
2.檢查試卷是否存在:
- 使用 paper.containsPaper(paperId) 方法檢查是否存在給定的試卷 ID。
3.處理每個學生的答案:
- 遍歷每個學生的答案列表。
- 獲取學生 ID 和名字。
- 計算總分,並構建分數字符串。
4.處理每個問題的答案:
- 獲取試卷上的所有問題 ID 序列。
- 對每個問題進行處理:
-
- 檢查學生的答案。
-
- 檢查問題是否已被刪除。
-
- 檢查問題是否存在。
-
- 判斷答案是否正確,並計算分數。
-
- 輸出問題內容、答案內容及是否正確,並累加分數。
5.輸出最終成績資訊:
- 如果學生的名字存在,則輸出學生 ID、名字、各題得分和總分。
- 如果學生的名字不存在,則輸出學生 ID 未找到的訊息。
主類 (Main 類)
- 功能:
讀取標準輸入,並根據輸入處理邏輯進行處理。
處理完成後輸出結果。 - 方法:
主函式:從標準輸入讀取資料直到遇到“end”,然後呼叫輸出處理方法。 - 輸入輸出流程:
讀取輸入:從標準輸入讀取資料行。 |
處理輸入:根據輸入行的字首呼叫相應的處理方法。 |
題目處理:將題目新增到題庫中。 |
試卷處理:將問題及其分數新增到試卷中,並檢查總分是否為100。 |
學生資訊處理:將學生資訊新增到學生資訊表中。 |
答卷處理:將學生的答案新增到答卷記錄中。 |
刪除題目處理:標記題目為已刪除。 |
輸出處理:遍歷所有答卷記錄,並輸出每個學生的得分情況。 |
2.第五次大作業
概括:這次大作業又來了一個全新設計, 家居強電電路模擬程式設計。第一次迭代難度不是很大,差一分就拿到滿分了。之所以簡單,是因為電路結構變化:只有一條線路,所有元件串聯,處理起來相對簡單。此次家居強電電路模擬程式-1
使用了物件導向的設計思想,定義了多個類來表示智慧家居系統中的不同裝置(如開關、分檔調速器、連續調速器、白熾燈、日光燈和吊扇)以及一個基類Device。透過繼承和多型性,實現了不同裝置之間的共同特性和差異特性的抽象與表達。
家居強電電路模擬程式-1
類圖:
順序圖
設計分析
1.介面設計:
定義了一個Controllable介面,用於表示可以控制的裝置。實現了該介面的類需要提供一個control方法來處理控制命令。
public interface Controllable {
void control(String command); // 處理控制命令
}
這樣的設計使得系統能夠靈活地新增新的可控裝置,而無需修改現有的程式碼結構。
2.裝置連線與處理:
使用PinandDevice類來處理裝置的連線資訊和控制命令。該類維護了兩個對映:devices用於儲存所有裝置,connections用於儲存裝置之間的連線關係。提供了processConnection
方法來處理裝置連線資訊,以及processControlCommand
方法來處理控制命令。
3.電壓更新與狀態輸出:
每個裝置都有一個updateOutputVoltage
方法,用於根據裝置的特性和輸入電壓來計算輸出電壓PinandDevice類中的updateVoltages
方法會遍歷所有裝置,根據連線關係更新裝置的輸入電壓,updateOutputVoltage
方法來更新輸出電壓。提供了printDeviceStatus
方法來列印所有裝置的當前狀態。
程式碼思路
Device (抽象父類 )
- 功能:
定義了所有裝置的公共屬性和方法,如裝置標識、輸入電壓、輸出電壓等。 - 屬性:
id:裝置的唯一識別符號。 |
inputVoltage:輸入電壓。 |
outputVoltage:輸出電壓。 |
resistance:電阻。 |
- 方法:
updateOutputVoltage():抽象方法,由子類實現具體的更新輸出電壓的邏輯。 |
getOutputVoltage():獲取輸出電壓。 |
getId():獲取裝置標識。 |
setInputVoltage(double inputVoltage):設定輸入電壓。 |
注:定義基類Device:
包含裝置標識(id)、輸入電壓(inputVoltage)、輸出電壓(outputVoltage)和電阻(resistance)等屬性。
提供了構造方法、更新輸出電壓的抽象方法(由子類實現)、獲取輸出電壓和裝置標識的方法,以及設定輸入電壓的方法。
控制裝置 (繼承Device 類 )
Switch:開關裝置,可以切換狀態(開/關)。
- 屬性:state,表示開關狀態(0為開啟,1為關閉)。
- 方法:
updateOutputVoltage():根據開關狀態更新輸出電壓。 |
control(String command):處理控制命令,切換開關狀態。 |
toString():返回裝置狀態的字串表示。 |
GearSpeedRegulator:分檔調速器,可以調節檔位。
- 屬性:gear,表示檔位(0-3)。
- 方法:
updateOutputVoltage():根據檔位更新輸出電壓。 |
control(String command):處理控制命令,調整檔位。 |
toString():返回裝置狀態的字串表示。 |
ContinuousSpeedRegulator:連續調速器,可以調節連續檔位。
- 屬性:Gearposition,表示檔位(0.00-1.00)。
- 方法:
updateOutputVoltage():根據檔位更新輸出電壓。 |
control(String command):處理控制命令,調整檔位。 |
toString():返回裝置狀態的字串表示。 |
受控裝置 (繼承Device 類 )
IncandescentLamp:白熾燈,可以根據電壓差調節亮度。
屬性:brightness,表示亮度(0-200lux)。
方法:
updateOutputVoltage():根據電壓差更新亮度。
toString():返回裝置狀態的字串表示。
FluorescentLight:日光燈,可以根據電壓差調節亮度。
屬性:brightness,表示亮度(0或180lux)。
方法:
updateOutputVoltage():根據電壓差更新亮度。
toString():返回裝置狀態的字串表示。
CeilingFan:吊扇,可以根據電壓差調節轉速。
屬性:speed,表示轉速(0-360轉/分鐘)。
方法:
updateOutputVoltage():根據電壓差更新轉速。
toString():返回裝置狀態的字串表示。
每個裝置類都繼承自Device並實現了Controllable介面(如果可控)。
實現了各自的updateOutputVoltage方法和control方法(如果適用)。
提供了toString方法來返回裝置的當前狀態
PinandDevice(裝置管理類)
- 作用:管理所有裝置及其連線關係,處理控制命令,更新裝置電壓,列印裝置狀態。
- 屬性:
devices:儲存所有裝置的 Map。
connections:儲存裝置連線關係的 Map。 - 方法:
processConnection(String line):處理連線資訊,建立裝置並設定連線關係。 |
createDevice(String deviceId):根據裝置標識建立相應型別的裝置。 |
processControlCommand(String line):處理控制命令,呼叫相應裝置的 control 方法。 |
updateVoltages():更新所有裝置的電壓。 |
printDeviceStatus():按型別順序列印所有裝置的狀態。 |
getTypeOrder(Device device):獲取裝置型別的順序,用於排序。) |
注:這是程式碼核心部分,主要處理邏輯就在這段程式碼當中,下面我將詳細介紹這部分
- 資料儲存:
使用 LinkedHashMap 儲存裝置和連線關係,保持了插入順序,在後面處理過程需要從首部開始向後面進行處理。
連線關係儲存在 String 到 String 的對映中
private static Map<String, Device> devices = new LinkedHashMap <>(); // 儲存所有裝置
private static Map<String, String> connections = new LinkedHashMap <>(); // 儲存裝置連線關係
- 處理思路:
1.首先進行連線處理,透過引腳將裝置連線起來,使之構成一條串聯電路
2. 然後控制命令處理,透過控制命令處理,更新電路上的控制裝置狀態
3.再然後電壓更新,對電路上的裝置電壓進行更新後,根據裝置上的電壓確定工作狀態
4.最後裝置狀態列印,根據要求按順序輸出狀態
sortedDevices.sort((d1, d2) -> {
int typeOrder1 = getTypeOrder(d1);
int typeOrder2 = getTypeOrder(d2);
if (typeOrder1 != typeOrder2) {
return typeOrder1 - typeOrder2;
}
return Integer.compare(Integer.parseInt(d1.getId().substring(1)), Integer.parseInt(d2.getId().substring(1)));
});
排序邏輯:
首先,對於集合中的兩個裝置d1和d2,透過呼叫getTypeOrder方法獲取它們的型別順序(typeOrder1和typeOrder2)。
如果這兩個裝置的型別順序不同(typeOrder1 != typeOrder2),則根據它們的型別順序進行排序。具體地,返回typeOrder1 - typeOrder2的結果,這意味著型別順序較小的裝置會被排在前面。
如果兩個裝置的型別順序相同,則進一步比較它們的ID。這裡假設ID是一個字串,且從第二個字元開始(d1.getId().substring(1)和d2.getId().substring(1))是一個可以解析為整數的部分。這兩個整數透過Integer.compare方法進行比較,確保ID數值較小的裝置排在前面。
inputHandle(輸入處理類 )
- 作用:處理使用者輸入的連線資訊和控制命令。
- 方法:
input():讀取使用者輸入,呼叫 PinandDevice 的方法處理連線資訊和控制命令。
主類 Main
- 作用:程式入口,建立 inputHandle 物件處理使用者輸入,建立 PinandDevice 物件更新裝置電壓並列印裝置狀態。
3.第六次大作業
概括:這次是家居強電電路模擬程式的第二次迭代。第一次迭代只有一條串聯電路,此次迭代增加了並聯電路,就是串聯電路上接了並聯電路。受控裝置增加了落地扇。
家居強電電路模擬程式-2
類圖
順序圖
設計分析
此次難度大大加大,第一次迭代我是透過引腳來更新電壓,由於此次引腳外接多個引腳,透過引腳來更新裝置狀態不可取(縱使透過輸入引腳可獲得裝置輸入電壓,但是輸出電壓我們不知道,無法計算出電勢差)。我花了足足一個上午的時間,才想到了思路。
先從總電路著手,我把並聯電路看成受控裝置接在總電路上。由於總電路是一條串聯電路,把這一天單獨拿出來處理就簡單很多了。首先計算出總電路的電阻,然後計算出總電路電流,得到電流後,按順序處理總電路上的裝置。遍歷總電路上的每個裝置,用電流乘上對應電阻得到裝置電壓後,更新裝置狀態。由於我將並聯電路看出來一個裝置,故可得到並聯電路上的電壓。將並聯電路上的子電路看出一天天串聯電路,儲存在List集合中,跟總電路處理邏輯一樣,從list集合中拿出每條串聯電路進行處理。這樣操作之後,就更新了電路上每個裝置狀態。最後,按裝置id順序輸出所有裝置的狀態或引數。
寫完之後想想,真妙啊!這設計得就一個字:妙(我都想自己誇自己了😀)寫了這麼多次大作業,我發現設計是最重要的一環,程式碼編寫是次要的。好的設計,程式碼寫起來就有思路了,寫起來就很快。
程式碼分析:
Device (抽象父類 )
- 功能:
定義了所有裝置的公共屬性和方法,如裝置標識、輸入電壓、輸出電壓等。 - 屬性:
id:裝置的唯一識別符號。 |
inputVoltage:輸入電壓。 |
outputVoltage:輸出電壓。 |
resistance:電阻。 |
- 方法:
updateOutputVoltage():抽象方法,由子類實現具體的更新輸出電壓的邏輯。 |
getOutputVoltage():獲取輸出電壓。 |
getId():獲取裝置標識。 |
setInputVoltage(double inputVoltage):設定輸入電壓。 |
注:定義基類Device:
包含裝置標識(id)、輸入電壓(inputVoltage)、輸出電壓(outputVoltage)和電阻(resistance)等屬性。
提供了構造方法、更新輸出電壓的抽象方法(由子類實現)、獲取輸出電壓和裝置標識的方法,以及設定輸入電壓的方法。
控制裝置 (繼承Device 類 )
Switch:開關裝置,可以切換狀態(開/關)。
- 屬性:state,表示開關狀態(0為開啟,1為關閉)。
- 方法:
updateOutputVoltage():根據開關狀態更新輸出電壓。 |
control(String command):處理控制命令,切換開關狀態。 |
toString():返回裝置狀態的字串表示。 |
GearSpeedRegulator:分檔調速器,可以調節檔位。
- 屬性:gear,表示檔位(0-3)。
- 方法:
updateOutputVoltage():根據檔位更新輸出電壓。 |
control(String command):處理控制命令,調整檔位。 |
toString():返回裝置狀態的字串表示。 |
ContinuousSpeedRegulator:連續調速器,可以調節連續檔位。
- 屬性:Gearposition,表示檔位(0.00-1.00)。
- 方法:
updateOutputVoltage():根據檔位更新輸出電壓。 |
control(String command):處理控制命令,調整檔位。 |
toString():返回裝置狀態的字串表示。 |
受控裝置 (繼承Device 類 )
IncandescentLamp:白熾燈,可以根據電壓差調節亮度。
- 屬性:brightness,表示亮度(0-200lux)。
- 方法:
updateOutputVoltage():根據電壓差更新亮度。
toString():返回裝置狀態的字串表示。
FluorescentLight:日光燈,可以根據電壓差調節亮度。
- 屬性:brightness,表示亮度(0或180lux)。
- 方法:
updateOutputVoltage():根據電壓差更新亮度。
toString():返回裝置狀態的字串表示。
CeilingFan:吊扇,可以根據電壓差調節轉速。
- 屬性:speed,表示轉速(0-360轉/分鐘)。
- 方法:
updateOutputVoltage():根據電壓差更新轉速。
toString():返回裝置狀態的字串表示。
FloorFan:落地扇,可以根據電壓差調節轉速。
- 屬性:speed,表示轉速(0-360轉/分鐘)。
- 方法:
updateVoltage():根據電壓差更新轉速。
toString():返回裝置狀態的字串表示。
電路管理類
SeriesCircuit:串聯電路類,管理串聯電路中的裝置。
- 屬性:
id:串聯電路的唯一識別符號。 |
resistance:串聯電路的總電阻。 |
connections:儲存裝置連線關係的 Map。 |
devices:儲存串聯電路中的裝置的 Map。 |
current:流經串聯電路的電流。 |
inputVoltage:輸入電壓。 |
outputVoltage:輸出電壓。 |
- 方法:
setInputVoltage(double inputVoltage):設定輸入電壓。 |
total_resistance():計算串聯電路的總電阻。 |
getResistance():獲取總電阻。 |
setCurrent(double current):設定電流。 |
getCurrent():獲取電流。 |
getConnections():獲取連線關係。 |
getDevices():獲取裝置。 |
ParallelCircuit:並聯電路類,管理並聯電路中的裝置。
- 屬性:
circuits:儲存並聯電路中的串聯電路的 List。 - 方法:
total_resistance():計算並聯電路的總電阻。 |
getCircuits():獲取串聯電路。 |
updateVoltage():更新並聯電路中的裝置狀態。 |
PinandDevice(裝置管理類 )
- 作用:管理所有裝置及其連線關係,處理控制命令,更新裝置電壓,列印裝置狀態。
- 屬性:
SeriesCircuitsheet:儲存所有串聯電路的 Map。 |
ParallelCircuitsheet:儲存所有並聯電路的 Map。 |
ALLdevices:儲存所有裝置的 Map。 |
- 方法:
processConnection_S(String line):處理串聯電路的連線資訊。 |
processConnection_p(String line):處理並聯電路的連線資訊。 |
createDevice(String deviceId, Map<String, Device> devices):根據裝置標識建立相應型別的裝置。 |
processControlCommand(String line):處理控制命令,呼叫相應裝置的 control 方法。 |
Update_circuit_resistance():更新電路的電阻和電流。 |
printDeviceStatus():按型別順序列印所有裝置的狀態。 |
getTypeOrder(Device device):獲取裝置型別的順序,用於排序。 |
注:此部分程式碼是主要部分,我將對邏輯處理思路進行分析
SeriesCircuitsheet:儲存所有串聯電路物件的 Map。
ParallelCircuitsheet:儲存所有並聯電路物件的 Map。
ALLdevices:儲存所有裝置物件的 Map。
private Map<String,SeriesCircuit> SeriesCircuitsheet = new LinkedHashMap<>();//儲存串聯電路物件集合
private Map<String,ParallelCircuit> ParallelCircuitsheet = new LinkedHashMap<>();//儲存並聯電路物件集合
private Map<String, Device> ALLdevices = new LinkedHashMap<>();//電路中所有裝置物件
- 在processConnection_p方法中,處理並聯電路的連線資訊,將串聯電路物件新增到並聯電路中,使用List集合介面儲存串聯電路物件
private List<SeriesCircuit> circuits = new LinkedList<>();//並聯電路中儲存了串聯電路#M1[T1 T2]
- 在Update_circuit_resistance()中更新電路的電阻和電流。首先遍歷所有並聯電路物件,
total_resistance
方法計算每個並聯電路的總電阻,然後使用 List 進行倒序遍歷 SeriesCircuitsheet,再然後呼叫updateVoltage
方法更新每個裝置的輸出電壓。根據輸入電壓和總電阻計算總電路電流。最後呼叫printDeviceStatus()
根據總電路電流和裝置電阻更新每個裝置的電壓和狀態。 - 短路與斷路處理:
使用-1表示電阻無窮大,代表電路斷開斷路。如果算得並聯電路電壓為0,則表示短路。在並聯電路中,用電壓為0處理每一個裝置,得到的自然就是未工作狀態
if(circuit.total_resistance()!=0) {
if(circuit.total_resistance()==-1)
continue;
inputHandle(輸入處理類)
- 作用:處理使用者輸入的連線資訊和控制命令。
- 方法:
input():讀取使用者輸入,呼叫 PinandDevice 的方法處理連線資訊和控制命令。
主類 Main
- 作用:程式入口,建立 inputHandle 物件處理使用者輸入,建立 PinandDevice 物件更新裝置電壓並列印裝置狀態。
- 方法:
main(String[] args):程式主方法。
三、採坑心得
1.寫答題判題程式最後一次迭代時,未注意到題目要求使用到繼承。由於未注意到此要求,我最開始沒有用到繼承。我將程式碼提交到pta後,最後幾個測試點實在改不出來就放棄了。後來,經同學提醒,此次大作業要使用到繼承,沒用到繼承,一律0分。我暈,pta是以提交的最高分那份程式碼為最終程式碼,這麼說來,不光要修改程式碼,我還得提高自己的分數來更新最終程式碼。於是,我開始調啊調啊,最後幾個測試點就是找不出來。無奈,我上部落格園搜別人寫得大作業總結,企圖找到有人跟我一樣,卡在那幾個測試點上。功夫不負有心人,火眼金睛的我終於找到了!按他的解決方法,一直沒過測試點終於過了。避免了0分的悲劇。哎~下次寫之前我一定要好好看清楚要求。
2.在寫家居強電電路模擬程式-1中,我是以引腳來更新裝置狀態的,裝置的輸出引腳接下一個裝置的輸入引腳。以上一個裝置輸出引腳的輸出電平作為裝置的輸入電壓。由於第一次迭代只有一條電路,電路上只有控制與受控裝置。設計的時候,我想,改變電壓的裝置不可能接GND,受控裝置就只能接GND了。這就導致了我有幾個測試點一直沒過(開關也有可能接GND),這麼說,你可能還理解不了,下面這個例子可以說明:
把開關接GND後,由於我預設受控裝置接GND(預設輸出電壓為0),按照我的設計思路,D2引腳接了VCC,那麼輸入電壓為220v,這樣在D2上算出來的工作電壓為220v,工作狀態自然就為滿幅輸出了
作業截至的時候,不知不覺才發現開關也有可能接GND啊。悔不當初啊!要是早想到就好了,這樣就可以拿到滿分了/(ㄒoㄒ)/~~悲劇啊!
3.家居強電電路模擬程式-2主要就是設計問題。設計好了,就簡單許多。開始最初我想延續第一次的思路,可是發現不行。因為裝置的輸出引腳可能接多個輸入引腳(這次考慮並聯),這樣就不好處理了。於是,我花了差不多三個小時設計思路,草稿本上畫了又畫,還是老師上課的時候就此題提了一嘴,可以透過計算電流或者採用分壓法來獲得裝置的工作電壓。我採用了前者,透過電流,就算出電壓。
此次大作業,我犯了一個致命錯誤,話不多說,直接看程式碼。
在case'A'分支中,我竟然未加break語句!導致了程式碼繼續執行 case 'M' 分支的程式碼,結果就是裝置物件被覆蓋,原本在 case 'A' 分支中建立的 FloorFan 物件會被 case 'M'中 ParallelCircuitsheet.get(deviceId) 返回的物件覆蓋,簡而言之就是裝置A未建立,裝置A的例項為空!編譯是沒任何問題的,我懷著自信提交程式碼,本以為可以拿到較高分數,沒想到,只有6分!6分啊!其餘測試點全是非零返回。由於此次測試點沒有提示,我一直找不到哪裡出現錯誤。也怪我自己,在測試程式碼的時候,沒有在測試樣例中加上裝置A。故事的最後,感謝同學送來的堪稱神之一手的測試樣例,他的測試樣例包含了裝置A。我將他的樣例一輸入,程式碼裡面就報錯,裝置A例項為null,為建立。經過除錯,才找到這個致命問題。感謝同學!感謝上天!
四、改進建議
1.家居強電電路模擬程式-1
類命名和職責:
重新命名 PinandDevice 類為更直觀的名稱,如 CircuitManager。
考慮將連線處理、控制命令處理和電壓更新等功能拆分為獨立的類或模組。
連線處理:
改進 processConnection 方法,確保正確處理所有引腳連線,並考慮 GND 引腳。
使用更靈活的資料結構來儲存連線關係,如 Map<String, List
控制命令處理:
考慮使用命令模式或策略模式來處理不同型別的控制命令。
在 Device 類中新增一個方法來檢查裝置是否支援特定的控制命令。
電壓更新:
考慮使用更復雜的電路模擬演算法來更新電壓,特別是在處理複雜電路時。
錯誤處理:
新增錯誤處理邏輯,如解析連線資訊時的異常處理。
2..這三次大作業使用到了大量的正規表示式匹配,有的複雜一點的匹配演算法是藉助ai完成的,運用正規表示式並不熟練。在課後,需要去更多的瞭解有關正則的方式匹配。
3.我發現我的程式碼重複性太高,一段程式碼,多次出現,程式碼冗餘嚴重。在今後的大作業中應避免該問題,減少程式碼冗餘並提高程式碼複用性,透過使用裝置工廠類和提取公共邏輯,讓程式碼變得更加簡潔和易於維護。同時,增加異常處理和日誌記錄,提高了程式碼的健壯性和可除錯性。
五、總結
這三次大作業寫完,我發現會寫程式碼不是主要的,會設計才是重要的一環(當然,這是本人的看法)。良好的 Java 設計不僅僅是編寫功能正確的程式碼,更重要的是確保程式碼的可維護性、可讀性、可重用性、效能、安全性和可擴充套件性。僅有編寫程式碼的能力是不夠的。設計在Java開發中同樣至關重要。就比如這次的家居強電電路模擬程式-2,設計好了,你才知道怎麼下手去寫。沒設計好,就開始寫,縱使寫到最後,也是一包渣。寫家居強電電路模擬程式-2的時候,我花了大量時間去設計,去構思該如何去寫,要設計成怎麼的電路,處理控制裝置的命令後要怎麼去更新受控裝置的電壓及狀態。這些時間花掉是值得的,程式碼第一次提交就拿到了91分(不考慮上述我犯的致命錯誤)。總而言之,設計至關重要,一個良好的設計能夠使系統更加清晰、易於維護,並且能夠更好地適應未來的變化。
這三次大作業都使用到了繼承與多型。可見其重要性。綜合以前幾次大作業,java的三個重要思想:封裝,繼承,多型也算是都深入瞭解並使用到了。透過使用繼承與多型這兩個物件導向程式設計的核心概念,程式碼複用大大提高了。繼承允許我們建立一個新的類(子類)來繼承一個已存在的類(父類)的屬性和方法。這樣,子類就可以複用父類的程式碼,避免了重複編寫相同的程式碼,提高了開發效率。多型性允許我們使用父類型別的引用來指向子類物件。這樣,我們可以在執行時動態地決定呼叫哪個類的方法,實現了介面的多種不同表現形式或行為。多型性增強了程式的靈活性和可擴充套件性。
學習到了抽象類和介面的使用,抽象類是用關鍵字abstract修飾的類,它不能被例項化。抽象類中可以包含抽象方法(沒有方法體的方法,用abstract修飾),透過子類重寫抽象方法,從而達到多型,提高程式碼的靈活性。介面通常用於定義一組相關的行為或功能,這些行為或功能可以由不同的類來實現。在家居強電電路模擬程式中我定義了處理控制命令介面。介面中有void control(String command)
方法。透過三個控制裝置實現介面,重寫介面中方法,從而達到多型,提高程式碼的靈活性。總而言之,抽象類和介面在Java程式設計中都具有重要的作用,它們各自具有獨特的特性和使用場景。