java二階段總結(電路模擬程式)

夏日救星發表於2024-11-23

前言

  • 第四次題目集

    1. 知識點
    • 開始應用繼承在大題目中,但對於方法重寫,多型的要求未涉及

      繼承是物件導向的三大特徵之一,可以使得子類具有父類的屬性和方法,還可以在子類中重新定義,追加屬性和方法。

      繼承是指在原有類的基礎上,進行功能擴充套件,建立新的型別。

    • 方法過載:第一題setYearsetMonthsetDay方法透過接受不同引數來實現方法過載。

    1. 題量:正常

    2. 難度:有個測試點很難想到,其他的都還好,只要學會繼承就會很好寫

  • 第五次題目集
  1. 知識點
  • 加深繼承的學習,很多地方需要應用方法重寫,多型更是基操,同時引入介面,第二題ArrayList 實現了 List 介面,提供了新增、獲取元素等方法。

    • 方法重寫形如powerOngetShow

    • 繼承形如series類繼承自circuit類,control類繼承自appliance類,SwitchgearControlcontiControlequiplampfan等類繼承自它們的父類。

    • 多型例子:appliance類和其子類展示了多型性,其中appliance作為父類,其方法在子類中被重寫。

  • 同時也系統的學習了組合模式(又叫作整體-部分模式,它將物件組合成樹狀的層次結構,用來表示整體-部分的關係,使使用者對單個物件和組合物件具有一致的訪問性,屬於結構型模式)

  1. 題量:正常

  2. 難度:如果是不思考後續設計直接寫,應該比較簡單,但老師再三強調需要思考後續設計,所以在設計上花費了很多時間 ,對比答題判題系列,電器系列的難度有斷層增加,之前可以直接思考大概思路就上手,寫程式碼佔比高,但這題開始就要先花大量的時間理思路,反而後面上手寫會比較輕鬆。確實是設計大於程式碼,但由於題目的要求不會特別高,比如,只有串聯電路,不會亂序輸入等等,所以也算是很好的二階段題目

  • 第六次題目集

    1. 知識點
    • 抽象類:當一個類中給出的資訊不夠全面時,它給出的資訊不足以描繪出一個具體的物件,不能例項化該類,這種類就是抽象類

    • 抽象方法:當父類需要定義一個方法,卻不能明確該方法的具體實現細節而定義的只有方法宣告,沒有方法體的一類方法統稱為抽象方法,抽象方法用關鍵字abstract修飾

      如果某個類中已經出現了抽象方法,那這個類必須定義成抽象類

    • 多型:多型體現為父類引用變數可以指向子類物件,定義了一個父類型別的引用,指向新建的子類型別的物件,由於子類是繼承他的父類的,所以父類型別的引用是可以指向子類型別的物件的

      • 繼承或實現:在多型中必須存在有繼承或實現關係的子類和父類

      • 方法的重寫:子類對父類中的某些方法進行重新定義(重寫,使用@Override註解進行重寫

      • 基類引用指向派生類物件,即父類引用指向子類物件,父類型別指子類物件繼承的父類型別,或實現的父介面型別

    1. 題量:正常

    2. 難度:不錯不錯,需要的設計的時間多,所以測試點沒有偏的,改錯時間少,所以總時長也不會太多

    雖然乍一看給人無從下手的感覺哈哈,比如算電阻,算連通性,類之間的關係等等,每一種都讓人剛開始毫無頭緒。所以,需要花費在設計上的時間更長了,不僅要考慮方法能不能,還要考慮方法好不好,我大概是設計了三個小時以上才搭好框架理清思路開始上手寫,這個時候已經知道該寫什麼屬性方法,方法大概該怎麼寫,上手寫就很快了,而且有個好處就是沒什麼錯

    對比第一題,需要考慮的功能變多了,而且真的特別適合使用抽象類了,所以這也是一個特別好的鍛鍊抽象類使用的題目,

    對比第一題,這一題更需要畫流程圖,要在草稿上線判斷各種情況,即使第一題設計的很詳細了也需要改動很多地方,反正我至少算是中改

設計與分析

1.答題判題程式-4

題目分析:

在3的基礎上,新增單選題,多選題,填空題,最主要的是,輸入順序會改變了

多選題和填空題出現全對,半對,錯的判斷,這會影響答案的儲存結構和分值的計算。

必須用繼承

注意的點:

  • 題目會在試卷之後再輸出!這個要求我上一次的程式碼不能實現,上一次寫的是輸入試卷後直接把題目物件加到試卷物件裡,這樣就會導致加入題目時,如果題目不存在,最後就會輸出題目不存在,需要改動

  • 多張試卷資訊,所以需要建立試卷類,試卷list類來儲存

  • 輸出要按學生資訊來按序輸出,而輸入亂序,所以需要將答卷物件按一定順序儲存

  • 因為必須用繼承,所以需要學會多型的用法

程式碼分析:


  1. 剛開始使用繼承,十分不熟悉,在對於題目,選擇題,填空題,普通題的關係時,雖然使用了繼承和多型(將choiceQuestiontextQuestion初始化為question加入questionlist中),但是沒有使用方法重寫,而是在每個子類裡多寫了兩個新的isRight的方法(isTextRightisChoiceRight)達成和父類一樣的功能,這讓後面判斷題目正確性比較麻煩,離譜到還新建了兩個textQuestionListchoiceQuestionList類哈哈

  2. 對於多型的使用也理解不透徹,典型半桶水程式碼

  3. 在最開始就應該修改

case  "#Z:":
                    // 檢查選擇題格式是否正確
                    if(st.questionStandard(test, cstr) == 1){
                        choiceQuestion qq = new choiceQuestion(cstr);
                        clist.addChoiceQusttionmp(qq.getId(),qq);
                    }
                    else{
                        flag = 0;
                    }
break;
改為:
                        question qq = new choiceQuestion(cstr);
                        qlist.addQusttionmp(qq.getId(),qq);
  1. 上一次是在試卷輸入時判斷題目,修改為在輸入end以後再加入並判斷題目即可,這種題目需要常常用到標誌入list但本體後面入list的方法

新掌握:

使用到了set容器,主要是可以鍵-值查詢而且可以去重,用來記錄多選題答案,之後判斷正誤也由set來查詢

public Set<String> choiceQuestionSet = new HashSet<>();
  • 部分順序圖:

2.家居強電電路模擬程式-1

題目分析:

  1. 輸入一長串的連線資訊,裝置資訊不單獨輸入,包含在連線資訊中。

  2. 含有控制裝置模擬:開關、分檔調速器、連續調速器,受控裝置模擬:燈、風扇。

  3. 按開關、分檔調速器、連續調速器、白熾燈、日光燈、吊扇的順序依次輸出所有裝置的狀態或引數。

注意的點:

  1. 開關的開閉會導致整個電路斷路!所以需要在最開始進行一次連通性判斷,再進行通電操作

  2. 控制裝置只有輸入電位的要求,而受控裝置需要電壓差,後者對引腳的區分不明確

  3. 分檔調速器理解錯了啊啊,0--2(3/4)檔,不是沒有1檔!

  4. 連續調速器是兩位小數輸出,其餘都是整數輸出(截尾輸出,非四捨五入

  5. 裝置要按序輸出,所以應該設計優先順序

  6. 會有沒有接線的引腳,就預設接地,電壓為0V,所以在剛開始初始化的時候就要把引腳設為0V

程式碼分析:


  1. 由於在pickPin函式中使用了switch-case的方法導致方法最大複雜度增加,雖然判斷型別問題一次性解決很方便,但下次還是要考慮將他們分開寫

  2. 對於按序輸出,當時思考到會不會後續需要對各種裝置進行一系列別的操作,所以為每個裝置設立了list,方便輸出了但同時也複雜了,第二次改為優先順序輸出好多了

  3. 幾乎未使用到受控裝置和控制裝置大類,所以這可能就是我某個方法複雜度高的原因吧。

  4. 關鍵程式碼:

被子類重寫的通電方法,在儲存所有電路資訊後,再一次性為他們通電,更新這個電路的“當前電壓值“(太雞肋第二次程式碼已棄用),而且第二次也將串聯電路繼承電器類,整個方法都將改為powerOn

 public void charged(){
        ...
        for(appliance a : this.getApplianceList()){        // 遍歷電路中的所有電器裝置
            Pin in = a.getInPin();     // 獲取電器裝置的輸入引腳
            in.setVol(getNowVol());    // 設定輸入引腳的電壓為電路的當前電壓
            a.powerOn();    // 電器裝置通電
            Pin out = a.getOutPin();    // 獲取電器裝置的輸出引腳
            setNowVol(out.getVol());    // 設定電路的當前電壓為輸出引腳的電壓
        }
    }
  1. 對每個受控裝置,都設定一個獲取電壓差的方法,寫在equip
public double getDVol() {
        Pin in = getInPin();    // 獲取輸入引腳
        Pin out = getOutPin();  // 獲取輸出引腳
        if(in.getName().equals(out.getName())){ // 如果輸入和輸出引腳名稱相同,則電壓差為0
            DVol = 0;
        }
        else{
            DVol = abs(in.getVol() - out.getVol()); // 否則,計算並返回它們之間的絕對電壓差
        }
        return DVol;
    }

新掌握:

  1. 組合模式:

抽象構件(Conponent)角色:主要作用是為樹葉構件和樹枝構件宣告公共介面,並實現它們的預設行為。抽象構件宣告訪問和管理子類的介面;
樹葉構件(Leaf)角色:是組合中的葉節點物件,它沒有子節點,用於實現抽象構件角色中宣告的公共介面。
樹枝構件(Composite)角色:是組合中的分支節點物件,它有子節點。它實現了抽象構件角色中宣告的介面,它的主要作用是儲存和管理子部件,通常包含
Add()、Remove()、GetChild() 等方法。

  • 組合模式使得客戶端程式碼可以一致地處理單個物件和組合物件,無須關心自己處理的是單個物件,還是組合物件,這簡化了客戶端程式碼;

  • 更容易在組合體內加入新的物件,客戶端不會因為加入了新的物件而更改原始碼,滿足“開閉原則”;

在本程式碼中具體的應用就是索引裝置直接或間接繼承appliance類,甚至串聯電路與並聯電路

  • 部分順序圖:


2.家居強電電路模擬程式-2

題目分析:

需要考慮電阻:白熾燈的電阻為 10,日光燈的電阻為 5,吊扇的電阻為 20,落地扇的電阻為 20

新增模擬落地扇,新增並聯電路

線路中包含多個串聯起來的並聯電路

注意的點:

  1. 並聯電路只會有串聯電路,不會出現單個裝置的情況

  2. 並聯資訊所包含的串聯電路的資訊都在並聯資訊之前輸入,不考慮亂序輸入的情況

  3. 短路要考慮,而且當正常處理,因為不會產生無窮大的電流燒壞電路

  4. 調速器的輸入端只會直連VCC,而且最多隻一個調速器!後期可能會連在串聯電路里!因為題目只說了不在並聯電路里,可能在第一個串聯單路里。

  5. 不考慮輸入電壓或電壓差超過220V的情況,所以下次應該要考慮了

  6. 並聯電路和串聯電路的輸入相似但是是不同的!會一個框輸入三個以上!

程式碼分析:


  1. 對於本題,多使用抽象方法,定義了總抽象父類,要麼實現父類所有的抽象方法,要麼子類本身也定義成抽象類

總抽象父類appliance,下面定義了controlequip兩個抽象子類,剩下的都是實體類

  1. 對比上一次,架空了Pin類,因為有電壓和電阻,不需要引腳來傳遞電壓,而是直接由函式遞迴來實現電壓的分配,但還是沒刪pin類,因為不知道下次有沒有用哈哈

  2. 關鍵程式碼:

  • 對於調速器,因為題目規定只有一個而且連著VCC,所以我將其拆開來看,相當於電源的一部分,而不把其加入電路內裝置集合中(不過要加入總裝置集合appList裡輸出),在裝置集合類裡定義一個記錄調速器名字的屬性,在輸入時發現調速器,就記錄,最後遞迴求電壓時先作用調速器

    class appliances{
        public String adjustName = "NOTHING";   /*  調速器名字   */
        public List<appliance>circuits;        /*  裝電路,最後一個是總電路(需改)   */
        public HashMap<String,appliance> applianceMap;        /*  裝所有裝置,包括電路   */
        public List<appliance> appList;        /*  裝所有裝置,不包括電路  */
    ...
    
  • 對於按序輸出,需要按照優先順序和名字排序

     public void endShow(){
            Collections.sort(appList, new Comparator<appliance>() {      /*  排序,先比較優先順序,再比較名稱   */
                public int compare(appliance a1, appliance a2) {
                    if(a1.getPriority() != a2.getPriority()){
                        return a1.getPriority() - a2.getPriority();
                    }
                    else{
                        return a1.getName().compareTo(a2.getName());
                    }
                }
            });
    
  • 對於並聯電路的電阻計算

    短路:Access = 1,R = 0

    沒有一條路連通: Access = -1, R = -1

    只有一條通路:Access = 1, R = RSum

    正常:R = RMultiply / RSum (其實用倒數應該更方便)

新掌握:

  1. 學會使用工廠模式,屬於類建立型模式,透過靜態方法處理不同傳入引數,從而建立不同具體產品類的例項,外界呼叫工廠類的靜態方法,傳入不同引數建立不同具體產品類的例項
  1. 將物件的使用和建立過程分離開,實現解藕。客戶端不需要關注物件是誰建立的、怎麼建立的,只要透過工廠中的靜態方法就可以直接獲取其需要的物件。

  2. 將初始化例項的工作放到工廠裡執行,程式碼易維護, 更符合物件導向的原則,做到面向介面程式設計,而不是面向實現程式設計。

class applanceFactory {
    public static appliance getAppliance(char c, String name) {
        switch (c) {
            case 'K':
                return new Switch(name);
            case 'F':
                return new gearControl(name);
            case 'L':
                return  new contiControl(name);
            case 'B':
                return  new filamentLamp(name);
            case 'R':
                return  new sunLamp(name);
            case 'D':
                return  new ceilingFan(name);
            case 'A':
                return  new floorFan(name);
            default:
                return null;
        }
    }
}

可以再繼續學習抽象工廠類,降低耦合度。抽象工廠模式將具體產品的建立延遲到具體工廠類中,這樣將物件的建立封裝起來,可以減少客戶端與具體產品類之間的依賴,從而降低系統耦合度,有利於後期的維護和擴充套件。使用靜態工廠方法,可以形成基於繼承的等級結構。

  • 部分順序圖:

踩坑心得

1.(答題判題程式-4) 在一個函式需要兩個matcher的時候,搞混了matcher

2.(答題判題程式-4) list的初始化不要忘記了,記得在建構函式中新增,不然會報錯!

Exception in thread "main" java.lang.NullPointerException:
Cannot invoke "java.util.List.add(Object)" because "this.formQuestions" is null

3.(答題判題程式-4) 最後被卡的測試點真的很難想到,一直在猜,甚至都把多餘空格的輸入改的格式化了,還是不對

原輸出

改後

之後才意識到是填空題不需要刪除答案前後空格問題,而這個也分兩種情況:

  1. 輸入有值的情況下,你前後的空格不能刪

  1. 輸入為空或者全部空格的情況,按原判斷流程判斷,刪除全部空格

輸入全空格,輸出

4.(家居強電電路模擬程式-1) 對於分檔調速器,沒有限制檔位最高值,導致上調||下調過多錯誤,之後在方法體內設定到達最高階||最低階後調整無效的操作

5.(家居強電電路模擬程式-1) 最後被卡的一個測試點是關於開關的,想岔了,對於開關在電器之後的情況,如果開關沒閉合,並不是說電器能輸入220v,而是電壓為0v,當時理解成後面斷路就為0v,前面還是220v

改為,在總通電的charged函式中新增連通性判斷

for(String sw : switchss.switchTreeMap.keySet()){        // 遍歷 switchss 中的所有開關
            Switch swi = switchss.switchTreeMap.get(sw);    // 獲取 switchss 中的開關
            swi.powerOn();   // 通電開關
            if(swi.flag == -1){
                setNowVol(0);
                break;
            }    // 如果開關的 flag 為 -1,則將電路的當前電壓設定為 0
        }

6.(家居強電電路模擬程式-2) 因為設計的時候花了比較多的時間,所以該規避的情況很多都規避了,最後被卡的測試點就是短路的其中一種,return本來寫的break,導致R被賦值為0後再次被賦值為錯誤的數,而這個還是比較難發現的,因為如果第一條就是短路,那麼是對的,只有是中間或末尾的串聯電路短路,才會出錯,所以這也是我只錯了那系列的一個測試點的原因。

public void isAccess() {        /*  並聯電阻,連通計算,不連通跳過 */
...
        for(appliance a : parallelApps){
            if(a.getAccess() == 1){
                flag ++;    /*  標誌總並聯電路連通不連通,1是連通,原來flag = 0;*/
                if(a.getR() != 0) {        /*  有電阻的串聯電路 */
                    RSum += a.getR();    RMultiply *= a.getR();
                }
                else{                    /*  無電阻,短路,直接break */
                    setAccess(1);    setR(0);    return ;    }
            }
            else{    continue;  }             /*  不連通,找下一條,這裡和串聯不一樣 */
        }
        if(flag == 0){                  /*  沒有一條路連通 */
            setAccess(-1);    setR(-1);
        }
        else if(flag == 1){    setR(RSum);    }      /*    注意只有一條通路的話算串聯!   */
        else{    setR(RMultiply / RSum);    }
    }

改進建議

個人程式碼改進策略:

  1. 效能最佳化:對於頻繁呼叫的方法,可以考慮最佳化其效能,例如使用快取或其他最佳化技術。

  2. 方法拆分:如果某個方法過於龐大,可以考慮將其拆分為多個小方法,每個方法處理一個特定的功能。

  3. 類的設計和繼承:

  • 考慮使用介面來定義一些通用的行為,例如Powerable介面來表示可以通電的裝置。

  • 最佳化類的繼承結構,確保每個類的職責清晰,避免不必要的繼承。

  1. 程式碼重構:將重複的程式碼片段抽象成方法,減少程式碼冗餘。

  2. 程式碼清晰性:

  • 使用更具描述性的變數名和方法名,以提高程式碼的可讀性。
  • 對於複雜的邏輯,新增註釋來解釋程式碼的目的和邏輯。
  1. 解耦:減少類之間的依賴,比如appliances類不應該直接操作appliance類的內部狀態,而是透過方法呼叫。

總結

學到了:

  • 學會了方法重寫,就是當子類需要父類的功能,而子類有自己特有內容時,可以重寫父類中的方法,這樣,即沿襲了父類的功能,又定義了子類特有的內容,方法重寫呼叫的是子類的方法,如果想呼叫父類的原方法,寫:

    super.isRight(answer);
    

    但是要注意私有方法不能被重寫(父類私有成員子類是不能繼承的),子類方法訪問許可權不能更低

    • 透過子類物件訪問一個方法會先子類成員範圍找,如果找不到就在父類成員範圍找,如果都沒有就報錯,不隔代查
  • 對繼承的構造方法理解加深了,子類中所有的構造方法預設都會訪問父類中無參的構造方法!如果沒有無參構造方法,可以用super(引數)在子類無參方法呼叫父類有參方法

    • 子類初始化之前,一定要先完成父類資料的初始化

    • 每一個子類構造方法的第一條語句預設都是:super(),自己再寫也可以

  • 學會了抽象類與抽象方法的使用,抽象類中的成員只比非抽象類多一種抽象方法,其他都和非抽象類一樣。

    • 抽象方法不能再使用privatefinal 或者static關鍵字來修飾,即abstract不能與privatefinalstatic共同出現

    • 若父類中定義了一個抽象方法要求其所有非抽象子類都必須重寫該抽象方法

  • 學會了組合模式和工廠模式

  • 對多型的理解加深,多型成員變數:編譯執行看左邊,多型成員方法:編譯看左邊,執行看右邊

    1. 子類和父類存在同名的成員變數時,訪問的是父類的成員變數
    2. 子父類存在同名的非靜態成員方法時,訪問的是子類中重寫的方法
    3. 子父類存在同名的靜態成員變數成員方法時,訪問的是父類的成員函式
    4. 不能訪問子類獨有的方法

展望:

  • 回看第一次寫繼承的程式碼(答題判題程式-4),真的很搞笑,什麼都是學了一半,半懂不懂,現在已經能熟練地使用啦!甚至到了要學新的關係而換掉繼承有點依依不捨www,看到自己的進步很好,不過對於繼承,現在只會考慮能不能用繼承,而沒怎麼考慮適不適合用繼承,還需要更熟練的用其他關係才行呢

  • 一階段答題判題系列可以直接思考大概思路就上手,寫程式碼佔比高,二階段電器系列開始就要先花大量的時間理思路,反而後面上手寫會比較輕鬆。需要設計大於程式碼,設計好了,上手敲500行也能一氣呵成,要不然寫兩個呼叫關係的方法都磕磕絆絆,以後也要多花專精一下設計,選用合適的結構,23種設計膜式,需要多學學。

  • 要多學學介面呢,現在用抽象類,感覺對介面的運用還不熟練

對課程改進建議:

  • 想寫點必須用介面的題目

  • 老師建議的類的關係很有幫助,按這樣設計確實理的比較快,不過感覺自己對這些關係的掌握還是不太理想,會用,但沒那麼會想到

相關文章