生宿舍管理系統(tǒng)UML建模:從用例圖到類圖的關(guān)鍵要點(diǎn))
簡介一份以UML為核心的學(xué)生宿舍管理系統(tǒng)設(shè)計文檔適合軟件工程或面向?qū)ο蠓治雠c設(shè)計課程學(xué)習(xí)者也適合正在進(jìn)行同類課程設(shè)計的讀者參考用于理解和實(shí)踐從需求分析到系統(tǒng)建模的完整過程。整個資源包僅含一個Word文檔大小約452KB文檔按需求分析、系統(tǒng)用例模型、靜態(tài)模型等模塊組織內(nèi)容較為典型。已有228人學(xué)習(xí)瀏覽屬于課程實(shí)驗(yàn)/課設(shè)報告類的常用資料。文檔圍繞宿舍樓管理員、學(xué)生、系統(tǒng)管理員和其他用戶四類角色展開需求梳理覆蓋入住申請、退宿處理、宿舍分配、系統(tǒng)配置維護(hù)等關(guān)鍵業(yè)務(wù)場景并配有相應(yīng)用例圖和類圖。既能幫助讀者掌握UML在管理信息系統(tǒng)中的實(shí)際用法也可作為撰寫課程設(shè)計報告或準(zhǔn)備答辯時的結(jié)構(gòu)參考。1. 為什么學(xué)生宿舍管理系統(tǒng)要先把 UML 圖畫清楚“UML-學(xué)生宿舍管理系統(tǒng).doc”這類文檔交付物不是能直接跑起來的服務(wù)而是用圖承載的系統(tǒng)設(shè)計用例圖圈定業(yè)務(wù)邊界類圖固定數(shù)據(jù)與對象關(guān)系活動圖、時序圖把入住、調(diào)宿、報修這些跨角色流程講清楚。宿舍管理系統(tǒng)的難點(diǎn)集中在“床位狀態(tài)”“入住關(guān)系”“報修狀態(tài)”這類有生命周期變化的對象上只建表不畫圖很容易在字段設(shè)計階段漏掉狀態(tài)流轉(zhuǎn)。這篇文章面向正在做軟件工程課程設(shè)計、或者剛接手宿舍管理模塊維護(hù)的開發(fā)者我會按實(shí)際畫圖順序把 UML 建模中容易錯的參數(shù)與關(guān)系理順。2. 用 UML 用例圖圈定宿舍管理系統(tǒng)的業(yè)務(wù)邊界2.1 按角色收斂參與者不要按人頭畫找參與者時常見做法是先列出所有會用系統(tǒng)的人再按“能操作的用例集合”合并成角色。宿管老師、樓棟值班員、值班學(xué)生如果都能完成“查房登記”就合并成一個宿管參與者同一個人如果既管宿舍又管財務(wù)但財務(wù)權(quán)限只出現(xiàn)在水電費(fèi)錄入場景中就應(yīng)該拆成宿管與財務(wù)兩個參與者。角色合并的原則是權(quán)限邊界不是組織架構(gòu)UML 用例圖里的參與者用小人圖標(biāo)表示系統(tǒng)邊界外放參與者邊界內(nèi)放用例。新手最常犯的錯是在參與者里畫一個用戶然后把登錄、修改密碼、登出全部連到它身上。登錄是一個公共前置動作不是宿舍管理系統(tǒng)的業(yè)務(wù)用例“用戶”作為參與者粒度太粗會讓用例圖失去“誰能做什么”的信息。更合適的方式是直接以學(xué)生、宿管、管理員這些具體角色作為參與者登錄行為作為圖中用例的include子流程出現(xiàn)。2.2 核心用例清單先定動作再補(bǔ)關(guān)系學(xué)生宿舍管理系統(tǒng)的核心用例其實(shí)很固定。我通常先把用例編號寫好Word 文檔里畫圖與寫用例說明共用這一套編號避免圖上是“分配宿舍”文字說明里卻叫“宿舍安排”。下面的表是常見課設(shè)規(guī)模的用例清單可以根據(jù)實(shí)際需求增刪。編號用例名主要參與者觸發(fā)條件UC-01查看空床位學(xué)生學(xué)生打開住宿申請頁UC-02提交入住申請學(xué)生學(xué)生填寫申請并提交UC-03分配宿舍宿管有通過資格審核的申請單UC-04辦理入住宿管學(xué)生到樓棟前臺UC-05提交調(diào)宿申請學(xué)生學(xué)生對當(dāng)前宿舍不滿意UC-06審批調(diào)宿宿管收到調(diào)宿申請UC-07登記來訪宿管校外人員來訪UC-08提交報修學(xué)生宿舍設(shè)施故障UC-09處理報修宿管報修單處于待派單狀態(tài)UC-10維護(hù)樓棟信息系統(tǒng)管理員新增或調(diào)整宿舍樓棟UC-11水電費(fèi)錄入財務(wù)每月抄表后表格的價值在于逼你把“觸發(fā)條件”寫出來。觸發(fā)條件決定了用例的起點(diǎn)如果某個用例寫不出觸發(fā)條件說明它多半不是一個獨(dú)立用例而是一個步驟。UC-04 與 UC-03 的關(guān)系尤其容易混淆分配宿舍是宿管預(yù)先完成的工作辦理入住是學(xué)生到校后確認(rèn)身份并領(lǐng)鑰匙兩者都存在于真實(shí)業(yè)務(wù)流程里但在系統(tǒng)里確實(shí)可能是同一個頁面完成的。我一般傾向于保留兩個用例因?yàn)閰⑴c者的操作目的不同后續(xù)畫活動圖時分支也更清晰。2.3 include 與 extend把主流程之外的邏輯掛上去用例之間不是靠連線堆出來的。UML 給用例圖提供了兩類常用關(guān)系include表示基本用例在執(zhí)行過程中一定會調(diào)用另一個用例extend表示在特定條件下才觸發(fā)的可選行為。用 YAML 寫用例描述時我會把 include 和 extend 直接掛在配置里這樣代碼評審或者答辯時能一眼看到異常分支。UC-08: name: 提交報修 primary_actor: 學(xué)生 pre: 學(xué)生已登錄系統(tǒng) flow: - 選擇房間與故障類型 - 填寫故障文字描述 - 提交報修單 post: 報修單狀態(tài)為“待派單” include: - UC-LOGIN: 登錄態(tài)校驗(yàn) extend: - trigger: 故障描述不清晰或?qū)W生主動補(bǔ)充 steps: - UC-08-UPLOAD: 上傳現(xiàn)場照片include 箭頭從基本用例指向被包含用例extend 箭頭則從擴(kuò)展用例指向基本用例兩者都是虛線加構(gòu)造型標(biāo)注include、extend方向畫反是 UML 類圖和用例圖里最高頻的扣分點(diǎn)。include 適合抽公共流程比如所有操作都要做的登錄校驗(yàn)extend 適合放可選項(xiàng)比如上傳照片、加急催辦。如果用例之間只是動作上的先后順序就不需要畫任何連線直接寫在活動圖流程里即可。2.4 系統(tǒng)邊界不要亂圈外部能力不算系統(tǒng)用例用例圖還有一個隱藏要素是系統(tǒng)邊界即一個矩形框把系統(tǒng)內(nèi)的用例框起來參與者全部放在框外。邊界怎么畫決定了下游類圖要做多少東西。校園卡門禁如果由后勤系統(tǒng)控制本期宿舍系統(tǒng)只負(fù)責(zé)在入住登記時調(diào)用門禁接口那么“控制門禁”就不能作為一個完整用例畫進(jìn)邊界里最多在辦理入住的擴(kuò)展說明里寫一句“調(diào)用外部門禁接口”。反過來如果宿舍管理系統(tǒng)自己維護(hù)水電費(fèi)數(shù)據(jù)和催繳狀態(tài)那么“水電費(fèi)錄入”“欠費(fèi)提醒”就必須畫進(jìn)邊界。判斷標(biāo)準(zhǔn)只有一個這個狀態(tài)或功能是否由本系統(tǒng)自己完成。凡是只依賴外部服務(wù)的操作都不值得為它單獨(dú)建類、建表否則類圖里會多出一堆沒有歸屬的接口類。3. 用 UML 類圖固定宿舍管理系統(tǒng)的對象結(jié)構(gòu)與關(guān)聯(lián)方向3.1 從用例名詞里抽候選類再補(bǔ)三類職責(zé)拿到第 2 章的用例描述后我一般先圈出所有名詞學(xué)生、宿舍、床位、樓棟、報修單、水費(fèi)記錄、調(diào)宿申請、來訪登記、管理員。名詞不一定都成為類有些只是類的屬性比如“姓名”“性別”放在 Student 里“容量”放在 Dormitory 里。但“床位”在宿舍管理系統(tǒng)里最好獨(dú)立成類因?yàn)榇参挥小翱臻e/占用/維修”三種狀態(tài)把狀態(tài)掛在宿舍類上會讓Dormitory承擔(dān)大量重復(fù)判斷。候選類按 UML 的三類職責(zé)整理實(shí)體類負(fù)責(zé)持久化數(shù)據(jù)控制類負(fù)責(zé)業(yè)務(wù)邏輯邊界類負(fù)責(zé)與參與者交互。學(xué)生、宿舍、報修單是實(shí)體類DormitoryAssigner是控制類負(fù)責(zé)分配宿舍的算法StudentPortal是邊界類對應(yīng)前端頁面入口。課程設(shè)計里的類圖常見問題是只畫實(shí)體類控制類和邊界類缺失導(dǎo)致整個類圖看起來和數(shù)據(jù)庫 ER 圖沒有差別。候選詞類名方案類型說明學(xué)生Student實(shí)體類賬號、姓名、性別宿舍Dormitory實(shí)體類樓棟、房間號、容量床位Bed實(shí)體類床位號、狀態(tài)宿舍樓棟Building實(shí)體類若宿舍編號能表達(dá)樓棟則可并入 Dormitory報修單RepairOrder實(shí)體類狀態(tài)、故障類型、處理人調(diào)宿申請TransferRequest實(shí)體類原宿舍、目標(biāo)宿舍、審批狀態(tài)入住登記控制器CheckInService控制類承擔(dān)入住事務(wù)門戶接口DormPortal邊界類學(xué)生端頁面邏輯入口不是每個系統(tǒng)都需要 Building如果宿舍編號本身能表達(dá)樓棟比如A-302我會把樓棟簡化為 Dormitory 的屬性如果樓棟要單獨(dú)維護(hù)樓層數(shù)、值班室電話就需要 Building 類并與 Dormitory 建立 1 對多關(guān)聯(lián)。3.2 多重性與外鍵位置用 Python 類骨架驗(yàn)證建模類圖不是畫完就結(jié)束我會把實(shí)體類的多重性映射成一段 Python 類骨架用于驗(yàn)證關(guān)聯(lián)方向是否合理。下面的代碼與類圖對應(yīng)不追求可直接運(yùn)行只表達(dá)關(guān)聯(lián)和狀態(tài)from dataclasses import dataclass, field from datetime import datetime from typing import Optional dataclass class Student: student_id: str # 主鍵 name: str gender: str # M / F dormitory: Optional[Dormitory] None # 0..1學(xué)生不一定入住 dataclass class Dormitory: dorm_id: str building: str capacity: int beds: list[Bed] field(default_factorylist) # 組合宿舍刪則床位刪 occupants: list[Student] field(default_factorylist) # 關(guān)聯(lián)0..* dataclass class Bed: bed_id: str bed_no: int occupied: bool False # 狀態(tài)由業(yè)務(wù)方法維護(hù) dataclass class RepairOrder: order_id: str dorm: Dormitory reporter: Student status: str # 待派單 / 處理中 / 已完成 created_at: datetime field(default_factorydatetime.now) assignee: Optional[str] None # 處理人賬號類圖上的多重性需要兩端都標(biāo)注Student 到 Dormitory 是0..1Dormitory 到 Student 是0..*表示一個宿舍可以住 0 到多名學(xué)生。代碼里Optional[Dormitory]表達(dá)的就是學(xué)生端 0..1。外鍵位置在多端的表里即 Student 表存dormitory_idRepairOrder 表存dorm_id如果反著把學(xué)生列表存在宿舍表就會在多對多或一對多映射時多一張中間表增加不必要的復(fù)雜度。3.3 聚合與組合宿舍和床位的生命周期差異類圖關(guān)系里最容易爭論的是聚合和組合。區(qū)分并不復(fù)雜組合表示部分與整體同生命周期整體沒了部分也必須沒了聚合表示部分可以脫離整體存在。宿舍與床位是典型的組合關(guān)系Dormitory對象刪除時床位沒有獨(dú)立存在價值所以用實(shí)心菱形菱形畫在整體端即宿舍對象那一端。宿舍與學(xué)生之間則用普通關(guān)聯(lián)而不是聚合。學(xué)生退宿后學(xué)生對象仍然存在只是dormitory引用被置空兩者生命周期不綁定。如果建模時誤用組合業(yè)務(wù)圖上就會出現(xiàn)“退宿即刪除學(xué)生對象”的荒謬表達(dá)落庫后更會把學(xué)生檔案一起刪掉。我最常用的判斷方法是問一句刪除整體時部分還有沒有單獨(dú)存在的理由有理由用聚合或關(guān)聯(lián)沒理由用組合。3.4 方法要表達(dá)業(yè)務(wù)規(guī)則別讓類圖淪為 ER 圖類圖與數(shù)據(jù)模型最大的差異在于操作。屬性下面要寫方法而且方法應(yīng)當(dāng)承載業(yè)務(wù)規(guī)則。比如Dormitory.vacate_bed(bed_no)內(nèi)部要校驗(yàn)床位當(dāng)前狀態(tài)、更新occupied并解除學(xué)生關(guān)聯(lián)如果只寫getBedList()之類的 CRUD類圖就沒法回答“床位狀態(tài)在哪維護(hù)”這個問題。在課程設(shè)計的答辯里評審經(jīng)常問“某個狀態(tài)是誰改的”答案應(yīng)該落到某個具體類的方法上。我習(xí)慣在類圖的方法區(qū)只寫關(guān)鍵業(yè)務(wù)方法比如assign_student()、transfer()、generate_bill()而把getter/setter省略。這樣類圖既是設(shè)計圖也是代碼結(jié)構(gòu)的導(dǎo)航圖開發(fā)階段看到類名就知道該去哪里處理業(yè)務(wù)。4. 活動圖與時序圖還原宿舍管理的關(guān)鍵動態(tài)流程4.1 活動圖要素與“申請調(diào)宿”的泳道劃分類圖解決“有哪些對象”活動圖解決“流程怎么走”。活動圖的要素固定開始節(jié)點(diǎn)、活動節(jié)點(diǎn)、決策節(jié)點(diǎn)、合并節(jié)點(diǎn)、結(jié)束節(jié)點(diǎn)加泳道后還能表達(dá)職責(zé)劃分。學(xué)生宿舍管理系統(tǒng)的流程通常按“學(xué)生—系統(tǒng)—宿管”三個泳道組織泳道代表職責(zé)方節(jié)點(diǎn)落在哪個泳道就說明這個動作由誰執(zhí)行。常見的錯誤情況是不畫泳道也不寫轉(zhuǎn)移條件一條線把所有活動串完那樣活動和流程圖沒有區(qū)別。以“申請調(diào)宿”為例我通常先整理出流程文字再對照泳道畫圖學(xué)生發(fā)起申請系統(tǒng)校驗(yàn)入住時長是否滿 30 天宿管審批系統(tǒng)釋放原床位并分配目標(biāo)宿舍。整理成泳道責(zé)任表如下泳道活動節(jié)點(diǎn)轉(zhuǎn)移條件學(xué)生提交調(diào)宿申請無條件系統(tǒng)校驗(yàn)居住時長入住不足 30 天 → 拒絕申請滿 30 天 → 進(jìn)入宿管審批宿管審批調(diào)宿駁回 → 流程結(jié)束通過 → 交給系統(tǒng)執(zhí)行系統(tǒng)釋放原床位并分配新床位宿管通過后系統(tǒng)通知學(xué)生結(jié)果分配完成后畫圖時決策節(jié)點(diǎn)用菱形從決策節(jié)點(diǎn)出來的每條轉(zhuǎn)移線上必須寫條件[入住不足 30 天]和[滿 30 天]都要標(biāo)只標(biāo)一條會讓流程出現(xiàn)無條件的默認(rèn)分支語義含糊。合并節(jié)點(diǎn)可以復(fù)用菱形把兩個分支匯成一條若出現(xiàn)兩個菱形一前一后判別標(biāo)準(zhǔn)是前者分流后者并流視角不能混。4.2 并發(fā)流程用 fork 與 join新生報到的場景宿舍管理系統(tǒng)有一個經(jīng)常被忽略的并發(fā)場景新生辦理入住時宿管核驗(yàn)身份與系統(tǒng)初始化住宿檔案是可以并行做的。核驗(yàn)身份由宿管操作住宿檔案由系統(tǒng)生成兩者之間沒有數(shù)據(jù)依賴串行反而浪費(fèi)時間。UML 活動圖用一條水平粗實(shí)線表示 fork入口一個、出口多個多條流程再通過一條水平粗實(shí)線匯合稱為 join。加入 fork/join 時要注意的是匯合條件。別讓 join 出現(xiàn)只有一條路徑到達(dá)的情況比如核驗(yàn)身份失敗時流程直接終止而另一個分支還在等待這會形成死鎖語義。常見的處理是把失敗分支也引入 join帶一個[是否通過]條件或者明確畫終止節(jié)點(diǎn)。畫圖工具不檢查這類語義但它直接影響后續(xù)開發(fā)對異步任務(wù)的理解值得在評審時逐條問。4.3 報修處理的時序圖消息順序與返回消息活動圖強(qiáng)調(diào)分支時序圖強(qiáng)調(diào)對象之間的調(diào)用順序。以報修處理為例參與者是學(xué)生與宿管對象是DormPortal、RepairController、RepairOrder。我通常把消息做成編號序列保證對象間的每一次調(diào)用都有明確的返回關(guān)系編號發(fā)送者接收者消息方向1StudentDormPortalsubmitRepair(roomId, desc, phone)同步調(diào)用2DormPortalRepairControllercreateRepairOrder(dto)同步調(diào)用3RepairControllerRepairOrdersetStatus(PENDING)同步調(diào)用4DormAdminRepairControllerassign(repairId, worker)同步調(diào)用5RepairControllerRepairOrdersetStatus(PROCESSING)同步調(diào)用6RepairOrderStudentnotify(維修員已出發(fā))異步通知時序圖的生命線用豎虛線表示消息 1 之前一般有一個創(chuàng)建對象或進(jìn)入頁面的動作。如果對象是臨時創(chuàng)建的比如 RepairOrder 實(shí)例在消息 3 附近應(yīng)該畫創(chuàng)建符號create。UML 時序圖還允許返回消息畫成虛線箭頭但不要把每個同步調(diào)用都補(bǔ)一個返回線那樣圖會非常吵只在有返回值影響后續(xù)判斷時畫。報修場景中assign()的返回值決定是否通知成功因此編號 5 之前的返回線值得畫出來。4.4 用包圖管理類圖規(guī)模當(dāng)類數(shù)量超過 20 個時單張類圖很難閱讀。常見做法是把類圖拆包用 UML 包圖表達(dá)模塊邊界portal放邊界類service放控制類domain放實(shí)體類infrastructure放外部接口。包之間只保留依賴線包內(nèi)再各自畫類圖。宿舍管理系統(tǒng)按這個拆分后學(xué)生端、管理端、報表模塊各自獨(dú)立改動宿舍分配算法時不會牽扯報修模塊的包。5. 用 Visio 畫 UML 類圖并做三類一致性校驗(yàn)5.1 Visio 里建類圖的最小步驟用 Visio 畫 UML 類圖步驟不在多關(guān)鍵是幾個選項(xiàng)位置要對。新建時選擇“軟件和數(shù)據(jù)庫”分類里的“UML 類圖”模板如果版本找不到直接搜索模板關(guān)鍵字。把類形狀拖入畫布后右鍵打開“形狀數(shù)據(jù)”逐個填寫類名、屬性和操作??梢娦杂梅杻?nèi)置公有、-私有、#保護(hù)屬性的格式寫成- name : String方法的格式寫成 findVacantBed() : Bed。連線時從“UML 靜態(tài)結(jié)構(gòu)”模具里選關(guān)聯(lián)、聚合、組合、依賴。雙擊關(guān)系線可以設(shè)置關(guān)聯(lián)兩端的多重性1、0..1、*、1..*都從這里填務(wù)必兩端都設(shè)置很多畫圖的人只填一端類圖語義就缺了一半。聚合和組合的菱形是整體端先確定哪個類是整體再把菱形放在整體類的這一端。5.2 三類一致性校驗(yàn)讓圖經(jīng)得起追問畫完圖以后我一般做三類檢查。第一類是類圖與用例圖的對應(yīng)找到用例表中每個動作確認(rèn)有某個類的方法能支撐找不到方法的用例往往意味著存在隱式控制類沒畫。第二類是關(guān)聯(lián)的雙向檢查每一條關(guān)系線都看兩端多重性是否與實(shí)際業(yè)務(wù)一致比如一份報修單只能歸屬一個宿舍報修單端寫0..*而宿舍端寫1方向反了。第三類是動態(tài)圖與狀態(tài)圖對賬活動圖中每個泳道角色必須出現(xiàn)在用例圖的參與者集合里時序圖的最終消息要與活動圖的結(jié)束狀態(tài)一致比如處理完報修后RepairOrder.status是否為COMPLETED。提示把各類圖里出現(xiàn)的類名、參與者名分別復(fù)制到兩個清單里做差集漏掉的往往不是一個小角色而是一條業(yè)務(wù)分支。Visio 畫的圖默認(rèn)是位圖導(dǎo)出如果文檔后續(xù)要交給別人維護(hù)建議保留原文件的同時把每個形狀的“形狀數(shù)據(jù)”也補(bǔ)全。檢查順序和三個清單互相參照的時間不長但可以省掉后續(xù)開發(fā)階段反復(fù)確認(rèn)語義的溝通成本。本文還有配套的精品資源點(diǎn)擊獲取