題目集4~6總結

残柠梦發表於2024-11-23

前言

經過三週的Java開發課程,我們逐漸從基礎的邏輯實現過渡到更復雜的系統設計,這不僅強化了我們對Java語言基礎的理解,還深入實踐了物件導向設計、異常處理、多執行緒和複雜資料結構等核心知識點。以下是對這三次作業的總結和反思。
第一次作業
題目開始引入複雜邏輯,包括試卷與題目關係的建立、標準答案判定、多種題型支援,難點是判定邏輯需要對不同題型(選擇題、填空題)的答案進行分類處理,知識點包括各模組之間的邏輯銜接,例如題目被刪除後如何影響答題結果、總分提示與答案判定的關係,確保多張試卷、多學生的情況下,結果按規則正確輸出。
第二次作業
本次是一個關於智慧家居的新系列,系統需要處理多個裝置,並且需要以串聯方式進行連線,需要動態輸入和狀態計算,知識點包括透過電路元件的輸入,進行一系列操作,去輸出相應狀態,難點就是怎樣透過鍵盤輸入去完成各個元件的串聯。
第三次作業
這次作業在上次基礎上,多迭代了一種電路元件,並且新增了一個並聯電路,輸入方式也有所變化,要多考慮一些分壓變化去輸出電路元件狀態,難點更多的是一些特殊情況,例如短路,斷路等等。

設計與分析

題目集4總共設計了七個類,包括問題,單選,多選,填空,試卷,學生,答卷
1.Question 類
該類是一個抽象類,所有題型(如單選、多選、填空)都繼承自它。
屬性:
number:題號。
content:題目內容。
方法:
scoreAnswer(String answer, int questionScore):抽象方法,負責根據學生提供的答案與正確答案對比,計算得分。具體實現由子類定義。
formatResult(String answer, boolean isCorrect):格式化輸出的結果。這個方法可以被子類重寫以定製不同型別題目的輸出格式。
2. SingleChoiceQuestion類
繼承自 Question,用於表示單選題。
屬性:
correctAnswer:正確答案。
方法:
scoreAnswer:根據學生提供的答案與正確答案比較,完全一致則得滿分,否則得0分。
formatResult:返回題目的格式化結果,包含答案是否正確。
3.MultipleChoiceQuestion類
繼承自 Question,用於表示多選題。
屬性:
correctAnswers:一個 Set,儲存正確答案的集合。
方法:
scoreAnswer:根據學生的答案和正確答案集合的比較,判斷答案是否完全正確、部分正確或完全錯誤:
完全正確:答案與正確答案完全匹配。
部分正確:提供的答案是正確答案的子集。
錯誤:存在錯誤答案。
formatResult:格式化結果輸出,區分完全正確、部分正確和錯誤的情況。
4.FillInTheBlankQuestion類
繼承自 Question,用於表示填空題。
屬性:
correctAnswers:一個 Set,儲存正確答案的集合。多個答案用 "或" 連線。
方法:
scoreAnswer:根據學生提供的答案與正確答案集合比較,判斷答案是否完全正確、部分正確或完全錯誤:
完全正確:提供的答案與正確答案完全一致。
部分正確:提供的答案是正確答案的子集。
formatResult:格式化結果輸出,處理正確和部分正確的情況。
5.TestPaper類
用於表示試卷,包含試卷編號、題目和總分等資訊。
屬性:
testNumber:試卷編號。
questions:一個 Map,將題號對映到對應的得分。
totalScore:試卷的總分。
方法:
addQuestion(int questionNumber, int score):向試卷中新增題目,指定每道題目的得分。
6.Student類
用於表示學生,包含學生的學號和姓名。
屬性:
studentID:學生學號。
name:學生姓名。
7.AnswerSheet類
用於表示學生的答卷,包含試卷ID、學生ID以及各題目的答案。
屬性:
testPaperID:試卷編號。
studentID:學生學號。
answers:一個 Map,儲存學生的答案,題號作為鍵,答案作為值。
方法:
addAnswer(int questionIndex, String answer):新增學生的答題資訊
類圖如下:
image
SourceMonitor報表如下:
image
程式碼行數為308行,語句數為169條分支語句百分比為21.9%(指的是條件分支語句的比例,例如 if/else 語句)方法呼叫語句數為96條(指的是程式碼中方法呼叫的次數),註釋行百分比為6.5%,類和介面數:5個(有部分類在主類裡面),每個類的方法數為2.80(每個類中方法的平均數量),每個方法的語句平均數為11.50最複雜方法的行號:83,最複雜方法的名稱為:FillInTheBlankQuestion.scoreA,該圖提供了多個指標的視覺化表示,註釋百分比:註釋比例較低,僅為6.5%,說明程式碼中的註釋較少。平均複雜度:複雜度適中,表明程式碼結構不至於過於複雜。每個類的方法數:每個類平均包含2.8個方法,表示類的結構比較簡單。平均深度:方法的平均巢狀深度適中,表明程式碼中的方法巢狀不深。最大深度:程式碼中出現的最大巢狀深度,具體數值無法從圖中獲取。最大複雜度:圖中顯示的最大複雜度,可能表示最複雜的程式碼塊或方法,該直方圖顯示了不同巢狀深度下的語句數量。大部分語句位於較淺的深度(深度為0到3之間),只有少量語句處於較深的巢狀層次。
題目集5設計8類,包括裝置,開關,風扇速度控制器,連續速度控制器,白熾燈,日光燈,風扇,電路
1.Device類
屬性:
String id:裝置的識別符號。
double inputVoltage:輸入電壓。
double outputVoltage:輸出電壓。
方法:
abstract void process():每個裝置都有自己處理並輸出其狀態的方法。
abstract void updateVoltage(double inputVoltage):每個裝置根據輸入電壓來更新其輸出電壓
2.Switch類
屬性:
boolean isClosed:表示開關是否關閉(開/關狀態)。
方法:
toggle():切換開關的狀態。
process():輸出當前開關的狀態(開/關)。
updateVoltage(double inputVoltage):根據開關的狀態更新輸出電壓。如果開關是閉合的,輸出與輸入電壓相同;如果開關是開啟的,輸出電壓為0。
3.FanSpeedController類
屬性:
int level:表示風扇的速度等級,範圍從 0 到 3。
方法:
increaseLevel():增加風扇速度等級,最大為 3。
decreaseLevel():降低風扇速度等級,最小為 0。
process():輸出當前的風扇速度等級。
updateVoltage(double inputVoltage):根據輸入電壓和當前的風扇速度等級更新輸出電壓
4.ContinuousSpeedController類
屬性:
double level:表示連續速度等級,範圍從 0.00 到 1.00。
方法:
setLevel(double level):設定速度等級,確保在 0.00 到 1.00 之間。
process():輸出當前的速度等級,表示為百分比。
updateVoltage(double inputVoltage):根據輸入電壓和當前的速度等級更新輸出電壓
5.IncandescentLight類
屬性:
double brightness:表示燈光的亮度。
方法:
process():輸出燈光的亮度值。
updateVoltage(double inputVoltage):根據輸入電壓調整亮度。如果電壓小於 9V,亮度為 0;如果電壓大於 220V,亮度為最大 200;否則,亮度根據電壓線性變化。
6.FluorescentLight類
屬性:
double brightness:表示燈光的亮度。
方法:
process():輸出燈光的亮度值。
updateVoltage(double inputVoltage):如果電壓大於 0V,亮度為 180;如果電壓為 0V,亮度為 0。
7.Fan類
屬性:
double speed:表示風扇的速度。
方法:
process():輸出風扇的速度。
updateVoltage(double inputVoltage):根據輸入電壓調整風扇的速度。
8.Circuit類
屬性:
Map<String, Device> devices:透過裝置 ID 儲存所有裝置。
List<String[]> connections:儲存裝置之間的連線關係。
方法:
addDevice(Device device):將裝置新增到電路中。
addConnection(String[] connection):新增裝置之間的連線。
processCommands(List commands):處理一系列命令,修改裝置的狀態。例如,切換開關或調整風扇速度等級。
simulate():模擬電壓在電路中的流動,並根據每個裝置的連線更新電壓。
printDeviceStates():輸出電路中所有裝置的狀態。
類圖如下:
image
SourceMonitor報表如下:
image
透過圖片而知,程式碼總行數為 322 行,程式碼中共有 225 條可執行語句,分支語句百分比表明24.9% 的語句為分支語句(如條件判斷),方法呼叫語句數表明程式碼中有 64 條方法呼叫語句,含註釋的行百分比表示只有 0.9% 的行包含註釋,表明程式碼中幾乎沒有文件說明,類和介面數表明程式碼中有 9 個類和介面,每個類的方法數表明每個類平均有 3.33 個方法,每個方法平均包含 5.33 條語句,最複雜的方法從第 235 行開始,方法名為 Circuit.printDeviceState,程式碼中的註釋比例僅為 0.9%,每個類的方法數相對較少,平均只有 3.33 個方法,這表明類比較大,每個方法平均包含 5.33 條語句,複雜度相對適中,但最複雜的方法位於第 235 行,這可能是程式碼中最難理解的部分。直方圖表明,大多數程式碼塊的語句數較少,複雜度較低,但也有一些程式碼塊深度較大或複雜度較高。
題目集6相比上次新增了落地扇類,並對電路類進行相應的修改,其餘沒有變化。
1.FloorFan類
屬性:
speed:風扇的速度
方法:
process():列印風扇速度
updateVoltage(double inputVoltage):根據輸入電壓更新速度
2.Circuit類
屬性:
devices:裝置的對映
seriesCircuits:串聯電路的對映
parallelCircuits:並聯電路的對映
方法:
addDevice(Device device):新增裝置到電路
addSeriesCircuit(String id, List<String[]> connections):新增串聯電路
addParallelCircuit(String id, List seriesIds):新增並聯電路
processCommands(List commands):處理指令
simulate():類比電路
simulateSeriesCircuit(String circuitId):模擬串聯電路
simulateParallelCircuit(String circuitId):模擬並聯電路
printDeviceStates():列印裝置狀態
類圖如下:
image

SourceMonitor報表如下:
image
總共有437行程式碼,總共有294條可執行語句,26.9%的語句是條件分支語句(例如 if、switch),共有85條方法呼叫語句,5%的程式碼行包含註釋,檔案中有2個類或介面, 每個類平均有21.5個方法,每個方法平均包含6.7條語句,最複雜的方法位於第402行,最複雜的方法是 Main.createDevice(),程式碼註釋的比例較低,僅為5%,該檔案的結構相對較複雜,語句中有近27%是分支語句,且有85條方法呼叫語句。每個類包含大量方法(21.5個),這可能意味著類承擔了過多的功能,可能需要透過重構來提高可讀性或模組化。最複雜的方法為Main.createDevice() 方法被標識為最複雜的方法,且位於第402行。考慮到它可能承載了檔案中的主要複雜性,可能需要重點最佳化或審查。

踩坑心得

1.最後一次題目類輸出時,,如果是多選題的話,輸出時只輸出一個答案,如下圖所示:
image
程式碼如下:

點選檢視程式碼
     public String formatResult(String answer, boolean isCorrect) {
            Set<String> providedAnswers = new HashSet<>(Arrays.asList(answer.trim().split("或")));
            if (isCorrect) {
                return super.formatResult(answer, true);
            } else if (correctAnswers.containsAll(providedAnswers) && !providedAnswers.isEmpty()) {
                return content + "~" + answer + "~partially correct";
            } else {
                return super.formatResult(answer, false);
            }
        }
    }

邏輯上分為三種情況,對正確,部分正確,錯進行處理,但是處理部分正確的答案的時候這裡應該是隻對第一個輸入的答案進行了輸出,輸出的話就是上圖情況,一時可能沒想到怎麼去解決。
2.第二次作業的樣例全可以透過,但是仍在一些測試點上無法透過。
image

點選檢視程式碼
class IncandescentLight extends Device {
    double brightness;

    public IncandescentLight(String id) {
        super(id);
        this.brightness = 0.0;
    }

    @Override
    public void process() {
        System.out.println("@" + id + ":" + (int) brightness);
    }

    @Override
    public void updateVoltage(double inputVoltage) {
        this.inputVoltage = inputVoltage;
        if (inputVoltage <= 9) {
            this.brightness = 0.0;
        } else if (inputVoltage > 220) {
            this.brightness = 200.0;
        } else {
            this.brightness = (inputVoltage - 10) * (150.0 / 210) + 50;
        }
    }
}

class FluorescentLight extends Device {
    double brightness;

    public FluorescentLight(String id) {
        super(id);
        this.brightness = 0.0;
    }

    @Override
    public void process() {
        System.out.println("@" + id + ":" + (int) brightness);
    }

    @Override
    public void updateVoltage(double inputVoltage) {
        this.inputVoltage = inputVoltage;
        this.brightness = inputVoltage > 0 ? 180.0 : 0.0;
    }
}

class Fan extends Device {
    double speed;

    public Fan(String id) {
        super(id);
        this.speed = 0.0;
    }

    @Override
    public void process() {
        System.out.println("@" + id + ":" + (int) speed);
    }

    @Override
    public void updateVoltage(double inputVoltage) {
        this.inputVoltage = inputVoltage;
        if (inputVoltage < 80) {
            this.speed = 0.0;
        } else if (inputVoltage >= 80 && inputVoltage <= 150) {
            this.speed = (inputVoltage - 80) * (280.0 / 70) + 80;
        } else {
            this.speed = 360.0;
        }
    }
}

感覺邏輯上沒有問題,IncandescentLight 類更新輸入電壓。根據輸入電壓計算亮度:低於或等於 9V,亮度為 0。高於 220V,亮度為 200。介於 10V 和 220V 之間:根據公式計算亮度,FluorescentLight類更新輸入電壓。如果輸入電壓大於 0,亮度為 180;如果輸入電壓為 0,亮度為 0,Fan類更新輸入電壓。如果輸入電壓小於 80V,轉速為 0;如果在 80V 到 150V 之間,根據公式計算轉速;如果超過 150V,轉速為 360,測試樣例中有關這三個的也進行了測試,也符合輸出,但是仍不知道問題所在,比較令人苦惱
3.在新加入新電路元件和並聯電路,分壓出現了問題
image
對於獲取串聯電路連線資訊

點選檢視程式碼
List<String[]> connections = seriesCircuits.get(circuitId);
if (connections == null) return;
計算總電阻
點選檢視程式碼
double totalResistance = 0.0;
for (String[] connection : connections) {
    String inputId = connection[0].split("-")[0];
    Device inputDevice = devices.get(inputId);
    if (inputDevice != null) {
        totalResistance += inputDevice.resistance;
    }
}
遍歷每個連線並更新裝置電壓
點選檢視程式碼
for (String[] connection : connections) {
    String inputId = connection[0].split("-")[0];
    String outputId = connection[1].split("-")[0];
    Device inputDevice = devices.get(inputId);
    Device outputDevice = devices.get(outputId);
    if (inputDevice != null && outputDevice != null) {
        double voltageDrop = voltage * (inputDevice.resistance / totalResistance);
        inputDevice.updateVoltage(voltageDrop);
        voltage -= voltageDrop;
        outputDevice.updateVoltage(voltage);
    }
}
邏輯上對於串聯電路是這樣,但是好像電壓輸出為0,達不到逾期效果。而對於並聯電路,首先是文字輸入不同

獲取並聯電路的資訊

點選檢視程式碼
List<String> seriesIds = parallelCircuits.get(circuitId);
if (seriesIds == null) return;
遍歷每個串聯電路
點選檢視程式碼
for (String seriesId : seriesIds) {
    List<String[]> connections = seriesCircuits.get(seriesId);
    if (connections == null) continue;
計算總電阻
點選檢視程式碼
double totalResistance = 0.0;
for (String[] connection : connections) {
    String inputId = connection[0].split("-")[0];
    Device inputDevice = devices.get(inputId);
    if (inputDevice != null) {
        totalResistance += inputDevice.resistance;
    }
}
但是輸出的話卻相差很多,應該還有值得改進的地方。

改進建議

要最佳化命名、增加註釋,提升程式碼的可讀性,有時候類分的也比較固定,容易限制思維,而對於這幾次題,給的測試樣例也逐漸減少,要多去自己想一些特殊情況,例如短路等等,感覺太侷限於測試樣例,彷彿是為了答案去解題,從而失去自己對題的一些正常看待,應該透過過程推結果,思維多發散一下。

總結

最後一次試卷類全新的大作業進行迭代,加入了多選題之類的迭代,透過分離不同的功能模組來進行操作,而全新的兩次迭代,學會了單一職責原則和開閉原則,對類進行封裝和繼承。java學習任重而道遠,需要不斷在一次次修改,迭代中學會更多,以後應該為大作業留一些更多時間,先大概有個思路,最後不斷去塑造出程式碼。