前言:
在本學期,我第一次接觸到Java這門面向對象的編程語言,此前我已經持續使用了兩年多的面向過程的C語言編程,並一直認為掌握好C語言這一門就能解決大部分的問題,但隨着這幾次從用Java語言實現的電梯調度程序再到每一次的迭代,我逐漸意識到Java這門編程語言確實有它的獨到之處,並且在一些複雜問題的實現上與C語言相比有更多的優勢。就比如在第一次的題目集中,從簡單的算法實現(如身份證校驗)和正則表達式匹配(驗證碼、QQ號),到面向對象的類設計(一元二次方程),最後是複雜的系統模擬(電梯調度)。涉及到字符串處理、數學計算、正則表達式、面向對象編程和狀態機管理等各個方面的知識點,題量也是從十幾行代碼的小任務逐步上升到上百行的大項目,在難度上由淺入深,前四題較為簡單,側重基礎技能,而最後一題電梯調度是用Java語言解決實際問題的綜合實踐,用到Java編程的很多知識,對於第一次接觸Java語言的我難度不小,然後是第二次的題目集,題目整體圍繞面向對象設計展開,逐步提升複雜度和實踐要求:7-1是基礎類設計,聚焦點和線的抽象,幫助鞏固封裝和基本方法;7-2引入設計原則(SRP),通過雨刷系統模擬訓練多類協作和狀態管理;7-3則是綜合性應用,將前期電梯問題迭代為多類設計,強調職責分離和調度邏輯。知識點覆蓋從類定義、屬性方法到設計原則和算法實現;題量逐題遞增,這時候我才真真切切的感受到Java編程的真正面目,最後是第三次的題目集,題目體現了從基礎業務邏輯到算法仿真,再到系統設計迭代的過程:7-1通過銷售佣金計算訓練面向對象設計和SRP原則應用;7-2引入蒙特卡羅方法,將數學理論與編程實踐結合;7-3作為電梯系統的第三次迭代,加入乘客類並改變請求格式,考驗系統架構的適應性和擴展性。知識點覆蓋商業邏輯、概率算法和複雜系統設計;題量逐題增加,7-3尤為龐大;難度也相比前2次題集增大了不少。接下來我將從代碼設計分析,踩坑心得,改進建議和總結四個方面,重點對三次的電梯調度的迭代開發進行詳細講述。
設計與分析:
第一次大作業:
類圖設計如下:
代碼參數分析如下:
思路分析:
整體設計思路
我首先思考的是如何用面向對象的方式來模擬電梯系統。電梯是一個很常見的現實物體,我覺得把它抽象成Java類應該挺合適的。電梯有狀態(當前樓層、運行方向),有行為(開門、關門、移動),還有要處理的各種請求。所以我就決定創建一個Lift類來封裝所有這些特性。
枚舉類型的選擇
我選擇用枚舉來表示方向,因為電梯的方向只有兩種固定的狀態:向上和向下。用枚舉比用字符串或者整數更安全,不容易出錯。比如如果我用字符串"UP"和"DOWN",萬一拼寫錯了編譯器也發現不了,但用枚舉的話,如果寫錯了Java會直接報錯,更便於我去定位代碼錯誤。
請求處理的思考
電梯要處理兩種請求:內部請求(電梯裏面的按鈕)和外部請求(樓層上的按鈕)。我覺得這兩種請求不太一樣,所以分別用不同的類來表示。內部請求只需要知道要去幾樓,外部請求還需要知道方向。
我用了隊列(Queue)來存儲這些請求,因為請求是按照時間順序來的,先來的請求應該先被處理,這正好符合隊列"先進先出"的特性。
電梯運行邏輯的設計
這部分是最難的!我花了很多時間思考電梯應該怎麼運行。我的想法是:
電梯應該先處理當前樓層的請求,然後再決定下一步怎麼走。如果當前樓層有請求,不管是內部還是外部的,都應該停下來處理。
關於方向的調整,我想到了現實中的電梯:當電梯向上運行時,如果上面沒有請求了,就會改變方向向下;反之亦然。所以我寫了一個方法來檢查當前方向是否還有未處理的請求。
去重邏輯的考慮
在實際使用電梯時,如果連續按同一個樓層的按鈕,電梯只會停一次。所以我在添加請求時加了去重判斷,避免同一個請求被多次加入隊列,同時當電梯運行到這一樓層時,應該將這一樓層對應的同方向外部請求及時的從外部請求隊列中移除。
輸入處理的思路
用户輸入可能有很多種情況,我需要考慮各種錯誤處理:
樓層範圍是否合法(最低層不能大於最高層),只有按規定的輸入格式<,>或<*>才能將對應的外/內請求加入到對應的隊列中。
樓層數字是否在有效範圍內,方向是否合法(不能在一樓按下行,頂樓按上行)
第二次大作業:
類圖設計如下:
代碼參數分析如下:
思路分析與改進:
這次我對電梯程序進行了徹底的重構,之前的版本把所有功能都塞在一個大類裏,感覺代碼有點亂,就像把所有的衣服都扔進一個櫃子,找起來特別麻煩,完全不符合Java“類”的有效使用,在經過了一週對Java的加深學習後,現在我學會了"單一職責原則",就是把不同的功能拆分到不同的類中,每個類只負責一件事情,這樣不僅可以使代碼的結構更清晰,也更方便後續的代碼複用和其他功能的添加和改進。
我設計了四個主要類:Lift負責電梯狀態,ReqList管理請求列表,ControlUnit控制電梯運行,Main處理程序入口。這樣分工明確,就像組建了一個小團隊,每個人各司其職。
枚舉類型的完善
我發現之前的方向枚舉只有UP和DOWN,但現實中電梯有時候會停在某層不動,所以我新增了IDLE狀態。還增加了Status枚舉來表示電梯是移動還是停止。這樣電梯的狀態描述更加精確了,就像給電梯裝上了更詳細的狀態指示燈。
請求管理的優化
這次的ReqList類讓我很有成就感!之前用隊列雖然簡單,但功能有限。現在我用了ArrayList,可以更靈活地管理請求。我還專門為外部請求創建了OutsideReq類,重寫了equals和hashCode方法,這樣在比較請求時更加準確。
去重邏輯也改進了,現在不是簡單比較最後一個請求,而是用equals方法全面比較,避免了重複請求的問題。大大的擴展了代碼的使用場景,可以更貼合現實中的電梯調度情況。
單例模式的應用
Lift類我用到了單例模式,因為整個系統只需要一個電梯實例,單例模式確保不會意外創建多個電梯,大大的貼合了題目的要求同時避免了意外創建多個電梯時產生的其他錯誤。
控制單元的智能化
ControlUnit是這個系統的"大腦",我花了很多心思來完善它。最大的改進是增加了循環次數限制,避免程序陷入死循環。之前有用題目樣例測試時,輸出結果出現了死循環的情況,所以這次我加了安全閥,更有效的避免了測試過程中帶來的死循環的情況,為後續為適應更多情況的程序迭代打下了基礎。
方向判斷邏輯也更加智能了。現在電梯會考慮兩種情況改變方向:到達邊界或者當前方向沒有請求。我還分別寫了檢查上方請求和下方請求的方法,邏輯更加清晰,代碼的複用性也大大的增強了。
狀態管理的精細化
我注意到之前電梯狀態輸出有點混亂,有時候會重複輸出相同狀態。現在我用lastPrintedLevel和lastPrintedDir記錄上次輸出的狀態,只有狀態變化時才輸出,這樣運行日誌更加清晰易懂,同時也更貼切了題目的具體要求。
電梯的移動邏輯也改進了,通過dirFlag參數控制移動行為,這樣可以在改變方向時進行特殊處理,這是針對輸入只有單個外部請求或內部請求的情況,具體是在探測具體的測試點要求中發現的。
請求處理的協同機制
當一個內部請求被處理時,我讓電梯自動清除同樓層同方向的外部請求。這個設計模擬了現實情況:如果有人從電梯內按了5樓,而5樓正好有人按了上行鍵,那麼電梯到達5樓時,這兩個請求應該同時被滿足,進而從對應的請求隊列中移除。
錯誤處理的加強
雖然在主程序中我還是簡單捕獲異常而不處理,但在請求驗證方面做了加強。現在會檢查樓層是否在有效範圍內,方向是否合法。我還特意處理了邊界情況,比如在最高層不能有上行請求,最低層不能有下行請求。
性能考慮的初步嘗試
我意識到如果請求很多,每次都遍歷整個列表可能會影響性能。雖然現在的數據量不大,但我還是做了一些優化,比如在檢查請求時儘量只查看第一個請求,而不是遍歷整個列表,當然,後續我發現題目的要求就是讓我們這麼做,這一點和現實中的電梯調度還是有一定的區別的。
測試友好性的提升
在ControlUnit的execute方法中,我加入了最大循環次數限制和警告輸出,這樣在調試時更容易發現問題。如果程序邏輯有問題導致死循環,至少會輸出警告信息而不是一直運行下去,這樣就便於更好的調試和對各種未知錯誤情況的提示,更快的定位到錯誤的情況並對其做出警告和後續相應的一些處理邏輯的添加。
面向對象思維的深化
通過這次重構,我真正體會到了面向對象編程的魅力。每個類都有明確的職責,類之間的關係清晰。如果以後要增加新功能,比如多部電梯協作,或者更智能的調度算法,只需要修改相應的類就可以了,不會影響到其他部分。
第三次大作業:
類圖設計如下:
代碼參數分析如下:
思路分析與改進:
這次最大的變化是我引入了"乘客"(Passenger)的概念,這讓我對電梯系統的理解又深了一層。
之前的版本中,請求只是簡單的樓層數字和方向,但現在我意識到,電梯服務的對象是乘客,每個乘客都有出發樓層和目標樓層。這樣的設計更貼近現實,也讓我更容易理解電梯的運行邏輯。
乘客概念的引入
這次我創建了Passenger類,我覺得這是新一層次的改進,每個乘客對象包含源樓層和目標樓層,如果是內部請求(乘客已經在電梯裏),源樓層就是null。這樣設計的好處是:
更符合現實邏輯:現實中的電梯就是運送乘客的,外部請求後進入電梯後就會緊跟着其對應的內部請求。
便於擴展:以後可以輕鬆添加乘客數量、重量等信息,為代碼的改進和迭代打下了基礎。
我還為乘客類添加了方向計算方法,可以根據源樓層和目標樓層自動判斷乘客是要上樓還是下樓,例如上樓的請求一定是內部請求樓層一定是大於對應的內部請求樓層,下樓請求就是內部請求樓層小於其對應的外部請求樓層。
請求處理邏輯的重大改進
這次最大的變化是外部請求的格式!之前是<樓層,方向>,現在是<源樓層,目標樓層>。這個改變讓程序邏輯更加清晰:
之前:只知道有人在3樓想上樓,但不知道要去幾樓
現在:知道有人在3樓想去5樓,信息更完整
這樣的設計讓電梯可以更智能地服務乘客。當電梯在3樓接上要去5樓的乘客後,會自動在5樓停下。
請求轉換機制的實現
我設計了一個很棒的請求轉換機制:當電梯響應外部請求停靠時,會自動將外部請求轉換為內部請求。比如:
有人在3樓按電梯要去5樓(外部請求)
電梯到達3樓,開門讓乘客進入
系統自動添加5樓的內部請求
電梯繼續運行,在5樓停下
這個機制模擬了現實中的電梯使用流程,讓程序更加真實。
電梯移動邏輯的優化
之前的move方法只有一個參數控制移動,現在分成了moveUp()和moveDown()兩個明確的方法。這樣做的優點是:
邏輯更清晰:一看方法名就知道電梯要做什麼
安全性更好:在每個方法內部都進行了邊界檢查
易於維護:如果需要修改移動邏輯,只需要修改對應的方法
請求隊列管理的改進
我放棄了ArrayList,改用LinkedList來管理請求。因為電梯請求的特點是頻繁在頭部添加和刪除,LinkedList在這種場景下性能更好。我還加強了去重邏輯,使用contains方法確保不會添加重複請求。
控制器邏輯的精細化
ElevatorController類的邏輯現在更加細緻了。我把大的方法拆分成多個小方法,每個方法只做一件事情:
checkIfShouldStop():檢查是否應該停靠
shouldStopForInternalRequest():檢查內部請求停靠
shouldStopForExternalRequest():檢查外部請求停靠
hasRequestsAbove()/hasRequestsBelow():檢查上下方請求
這種"分而治之"的思路讓代碼更容易理解和調試。
請求方向判斷的智能化
現在電梯的方向判斷更加智能了。不僅有邊界檢查(到達頂層或底層時改變方向),還會檢查當前方向是否還有未服務的請求。如果沒有,就自動改變方向。
我專門寫了hasRequestsAbove()和hasRequestsBelow()方法來分別檢查上方和下方的請求,這樣邏輯更加清晰。
錯誤處理的加強
在setCurrentFloor方法中,我添加了範圍檢查,確保不會把電梯設置到不存在的樓層。雖然這看起來是個小改進,但體現了"防禦性編程"的思想,讓程序更加健壯。
性能考慮的進一步優化
我增加了最大循環次數到10000,並改進了循環邏輯,確保在請求處理完後及時退出循環。還添加了更明確的警告信息,方便調試。
狀態管理的進一步完善
狀態輸出邏輯更加精確了,只有電梯的樓層或方向真正發生變化時才輸出狀態信息。這樣運行日誌更加簡潔,不會有一大堆重複的狀態輸出。
採坑心得:
在實現這三次大作業電梯調度程序的過程中我也遇到了不少的問題,具體如下:
問題1:對外部請求和內部請求的檢索處理邏輯效率和正確性
在三次迭代過程中,請求處理邏輯經歷了從簡單到複雜的演變,但各自都有一定的問題,在開始的時候,我嘗試着用暴力遍歷外部請求和內部請求的方法實現對請求的處理,但仔細研究題目和具體的電梯調度過程後,發現該題目對應的場景是一次只對兩個請求隊列中第一個元素進行判斷和處理,即"隊頭",這也導致此前使用的暴力遍歷的方案是錯誤的,最終的測試點是無法通過的,於是我對外內部的請求的相關處理方法進行了修改,具體的迭代過程和效果如下。
第一次迭代:請求概念模糊,內部請求和外部請求沒有明確區分,而是放在一個方法中實現,不便之處是調試困難,並且複用性差,代碼執行效率低。
第二次迭代:雖然區分了內外請求,但缺乏完整的請求生命週期管理,就比如在測試題目集3要求的測試樣例2是造成了如下的死循環的輸出結果
...
第三次迭代:引入了Passenger概念,但請求轉換邏輯複雜度增加,但有效解決了輸出死循環的情況
|
測試場景
|
第一次迭代
|
第二次迭代
|
第三次迭代
|
|
請求轉換正確性
|
不支持
|
部分支持
|
完全支持
|
|
請求去重效果
|
低
|
中
|
高
|
|
內存泄漏風險
|
高
|
中
|
低
|
心得總結:
概念建模的重要性:從簡單的"樓層數字"演進到"乘客請求",讓業務邏輯更加清晰
數據結構的演進:ArrayList→LinkedList的選擇體現了對性能特徵的深入理解
生命週期管理:請求的創建、轉換、銷燬需要完整的流程控制
問題2:系統架構的可維護性
隨着功能的增加和一次次迭代,代碼的可維護性和功能擴展難度成為重要挑戰:
嵌套耦合度過高:第一次迭代所有邏輯集中在Main類中,常常基本上修改一個地方或加一個功能,很多地方都要修改。
職責模糊:第二次迭代開始拆分,但類間職責劃分不夠清晰,這也導致後續在調試過程中,在定位錯誤代碼時有了較大的困難。
架構質量指標對比:
|
質量指標
|
第一次迭代
|
第二次迭代
|
第三次迭代
|
|
類數量
|
2
|
5
|
6
|
|
平均方法行數
|
28
|
15
|
12
|
|
單元測試覆蓋率
|
不可測
|
40%
|
75%
|
|
添加新功能工作量
|
高(8人時)
|
中(4人時)
|
低(2人時)
|
心得總結:
再結合SoureMonitor對代碼的分析結果,我總結出如下的結果:
單一職責原則的實踐價值:每個類只做一件事,大大提升可維護性
接口設計的重要性:良好的接口設計是擴展性的基礎
測試驅動開發的益處:先寫測試有助於設計更好的接口
重構的持續性:架構優化不是一次性工作,需要持續改進
改進建議:
架構設計層面的改進方向
在三次迭代開發過程中,我深刻認識到架構設計的重要性。為了確保代碼的可持續改進,我認為應該從以下幾個方面進行優化:
首先,我需要建立更加清晰的模塊邊界。雖然第三次迭代已經進行了較好的模塊劃分,但各模塊之間的依賴關係還可以進一步優化。比如,可以考慮引入依賴注入機制,讓ElevatorController不直接依賴具體的ElevatorSystem實例,而是通過接口進行交互,這樣在未來需要擴展多電梯系統時,可以更容易地進行改造。
其次,我意識到配置管理的重要性。目前電梯的參數(如樓層範圍、移動速度等)都硬編碼在代碼中,這不利於系統的靈活部署。理想的改進方向是將這些參數外置到配置文件中,甚至支持運行時動態調整。這樣不僅便於測試不同場景,也為未來的功能擴展(如不同樓宇的不同配置)奠定了基礎。
測試體系的完善策略
在測試方面,我認識到單純的功能測試是遠遠不夠的。為了確保代碼的可持續改進,我需要建立完整的測試體系:
單元測試覆蓋率需要進一步提升,特別是對於邊界條件和異常路徑的測試。我發現在第三次迭代中,雖然測試用例數量增加了,但很多邊緣情況(如併發請求處理、資源競爭等)還沒有得到充分覆蓋。應該建立測試用例庫,確保每次代碼修改都不會引入迴歸問題。
性能測試應該成為持續集成的一部分。目前的性能測試還是手動的、偶發的,應該建立自動化的性能基準測試,在每次重要提交後自動運行,監控關鍵指標的變化趨勢,及時發現性能迴歸問題。
代碼質量保障機制
在代碼質量方面,我需要引入更多的自動化工具來保障代碼質量。比如靜態代碼分析工具可以幫助發現潛在的技術不足,代碼格式化工具可以保持代碼風格的一致性。這些工具應該集成到開發流程中,在代碼提交前自動運行,確保只有符合質量標準的代碼才能進入代碼庫。
另外,我認為需要建立更加規範的代碼審查流程。在之前的開發中,雖然我也進行了一些自我審查,但缺乏系統性的審查清單和標準。應該建立代碼審查檢查表,涵蓋安全性、性能、可維護性等多個維度,確保每次代碼改進都是高質量的。
總結:
技術能力的全面提升
通過這三次電梯調度程序的迭代開發,我的技術能力得到了全面的提升。最大的收穫是從單純的"編碼思維"向"工程思維"的轉變。
在第一次迭代時,我主要關注如何讓程序運行起來,實現基本功能。到了第二次迭代,我開始思考代碼的結構和組織,嘗試用面向對象的思想來設計程序。第三次迭代時,我已經能夠從系統架構的角度思考問題,考慮可擴展性、可維護性等工程屬性。
這種思維層次的提升,讓我意識到軟件開發的複雜性不僅在於算法和語法,更在於如何組織代碼、如何管理複雜度、如何保證質量。這種認識將對我未來的學習和工作產生深遠影響。
對工程方法的深刻理解
在實踐中,我真正體會到了各種軟件工程方法的價值。迭代式開發讓我認識到,優秀的軟件不是一蹴而就的,而是通過不斷反饋和調整逐步完善的。單一職責原則、開閉原則等設計原則,從書本上的概念變成了我日常編碼的指導方針。
特別是在處理需求變化時,我深刻體會到了良好架構的重要性。第三次迭代相比第一次迭代,雖然代碼量增加了,但添加新功能的速度反而更快了,這就是良好設計帶來的好處。
問題解決能力的鍛鍊
在解決各種技術問題的過程中,我的問題解決能力得到了顯著鍛鍊。從最初的遇到問題就搜索答案,到現在能夠系統性地分析問題根源、設計解決方案、評估不同方案的優劣,這種能力的提升是課堂學習難以獲得的。
特別是在調試複雜邏輯錯誤時,我學會了如何使用分治法定位問題,如何設計可測試的代碼結構,如何編寫有效的單元測試。這些經驗對於我未來面對更復雜的技術挑戰具有重要意義。
需要進一步學習的方向
儘管取得了很大進步,但我也清醒地認識到自己的不足,需要在以下方面繼續深入學習:
首先,我需要系統學習設計模式。在開發過程中,我隱約感覺到某些場景下可以使用特定的設計模式來優化代碼,但由於掌握的設計模式有限,往往無法選擇最合適的方案。計劃下一步重點學習常用的設計模式及其適用場景。
其次,我需要深入學習軟件架構知識。目前的架構設計還比較基礎,對於分佈式系統、微服務架構等現代軟件架構瞭解不夠。隨着系統複雜度的增加,良好的架構設計能力將變得越來越重要。
另外,我需要提升性能優化技能。雖然第三次迭代在性能上有所改進,但主要是通過算法優化實現的,對於系統級的性能優化(如內存管理、併發控制等)還缺乏深入理解。