面向對象的照妖鏡——UML類圖繪製指南

1.前言

感受

在剛接觸軟件開發工作的時候,每次接到新需求,在分析需求後的第一件事情,就是火急火燎的打開數據庫(DBMS),開始進行數據表的創建工作。然而這種方式,總是會讓我在編碼過程中出現實體類設計疏漏的地方,導致我在寫業務代碼時,還回頭去反覆的修改數據表和實體類。爲了規避這樣的情況,我學習期間發現了UML中關於類圖的知識點,它讓我知道,作爲編碼者在分析需求後,做的第一件最基本的事情應該是進行面向對象分析,然後使用UML繪製類圖的方式進行面向對象的設計。在類圖繪製完之後,使用類圖與組員溝通設計思想,分析設計的可行性,在項目組一致達成共識後才進入後面的動手環節。

以上這種,通過面向對象分析和設計來繪製類圖的工作習慣,我一直延續至今。因爲,它不僅能保證軟件構建的穩定性,還能提升我們面向對象的思想和實踐能力。在實際中,極少數的情況下,公司會聘用專門的設計人員爲你提供設計方案,更多的情況是,程序員要擔任設計和編碼的綜合性工作,所以我認爲掌握UML類圖,是一名程序員的技能標配。

三個層次

在標準的軟件工程建模當中,類圖實際上根據三個層次劃分爲了三種類型的類圖,根據使用順序分別爲:概念層類圖、說明層類圖和實現層類圖。概念層用於業務建模階段,着重於對問題領域的概念化理解;說明層用於概念模型階段,主要考察類的交互涉及哪些接口;實現層用於設計階段,主要考慮類在代碼技術層面的實現細節。本文主要主要以實現層的類圖爲主,因爲實現層的類圖是最常用的,並且它是直接影響到我們實際的編碼工作的,下面我會針對它涉及的繪製方式、類之間的關係展開詳細講解。


2.類的識別

UML類圖的基本語法是很簡單的,可能懂點編程的人在不繫統學習的情況下,藉助繪圖工具都可以繪製出來。但在實際的業務需求中,充斥着各種晦澀的業務概念、事物,要從其中準確無誤的提煉出有利於業務系統的類,並非一件簡單的事情。

對於類的識別,並沒有很具體的步驟、公式進行照搬硬套,往往只能通過自身的經驗和麪向對象的造詣去識別類。並且識別類往往也不是一蹴而就的,還要結合類與類之間的關係、業務的使用場景,反覆推敲,才能逐步得到合適的類型。對此我只能提供一些概念性的經驗心得,讀者可以選擇性的參考,並不作爲一個標準。

類的識別很大程度上需要依靠“邊界”,這是一個複雜的概念,你可以簡單理解它相當於一個範圍,設定邊界可以讓我們知道能做什麼事情,和不能做什麼事情。並且邊界的設定會決定我們看待事物的視角和抽象事物的層次。對於類的識別而言,其邊界可參考當前的系統的目標、業務場景等,有了清晰的邊界,可以縮小類的識別範圍,不在是天馬行空,毫無根據。

如果不通過邊界確定一個角度,那麼對於同一事物,通過不同的角度會提煉出不同的類型。就拿我們自身舉例,從職業的角度來看我們則是程序員,從國家的角度來看我們則是中國人,從動物的角度來看我們則是人類。所以我們必須要通過邊界來確定一個角度,從而清晰的分析獲取有利於業務系統的類型。

 

例如,你需要在一個網課教育系統中,分析上課的場景中會有哪些類型。如果你不考慮邊界(網課教育系統中的上課場景),那麼你可能天馬行空的分析出:男人、女人這些類型。這樣分析出的類型和屬性顯然對系統毫無意義,也無法爲業務提供價值。如果你考慮到了邊界(網課教育系統中的上課場景),那麼你分析出的類型必然是在這個邊界內有利於業務的:老師、學生。

對於分析類中的成員(屬性、操作)也可以利用邊界來分析。還是以上面的網課教育系統爲例,如果不考慮邊界,很可能會對老師類和學生類分析出:體重、身高、髮量等無意義的屬性。只有你充分考慮邊界,你就會注重系統的目標、業務的場景,分析出對業務有價值的屬性,例如學生類的選修課程、老師類的教齡等。

如果你對邊界的概念還是比較模糊,那麼你可以在識別類的時候,嘗試將當前的系統目標、業務場景看作一個邊界,從而選擇合適的角度,去提煉出對業務系統有價值的類型。


3.外形

3.1.可見性

可見性主要用於標識類圖中的屬性和操作,通過設置不同的可見性決定外界對其的訪問程度,和編程語言中的訪問修飾符同理。UML規範定義了4種可見性,如下表所示。

 

3.2.類的表現形式

類在UML類圖中的形狀是一個矩形的方框,在方框中被分爲三段區域,上段主要是標識類的名稱,中段主要包含類的屬性(特徵),下段主要是包含類的是操作(行爲)。表示一個類時,三段區域的設定並不是必須的,可以只在矩形方框中寫一個類名,也可以只寫類名和屬性,或者是類名和操作。

 

3.3.代碼類型對應類圖

下面將使用C#編程語言編寫出:普通類型、抽象類、接口。然後體現出它們在類圖中的表現形式。

普通類

 

抽象類(類名和抽象方法名都是斜體

 

 

接口(名稱上方加<<interface>>)

 


4.關係

4.1.關聯關係

概述

我們以面向對象的思想去分析業務時,其中最基本的是,要弄清楚完成這個業務需要哪些對象。但是往往只分析出對象還遠遠不夠,因爲業務對象之間是相互獨立的。對象之間必須建立某種鏈接,促使它們相互協作通信,才能實現業務目標。而這其中用於鏈接對象的關係,就稱之爲關聯。換句話說,只要兩個對象之間存在關聯,那麼就意味着對象可以與它關聯對象進行通信,獲取對方的數據進行消息傳遞。

結構化

關聯定義了類之間的一種結構化關係,是一種天然存在的關係。(好比如人出生就有擁有一個國家和一對父母)通常在代碼中,關聯關係體現爲類的屬性,如A關聯B,那麼B的對象會作爲A對象的某個屬性。在例如在運用ORM框架的代碼中,類的關聯對象通常定義爲一個“導航屬性”,可以通過這個導航屬性獲取到關聯對象的數據。在例如數據庫中,表的關聯對象通常體現爲一個“外鍵”屬性,表的某行數據可以通過這個外鍵屬性獲取到關聯表的數據。不管是導航屬性或是外鍵,它們都是靜態的、天然存在的結構。

方向

默認的關聯關係是一條不帶箭頭的直線表示的,這代表着兩個關聯的類“知道”雙方的存在,並可以互相引用。在少數情況下,當兩個類之間只需要單方向鏈接來獲取消息時,就需要標識箭頭指向被鏈接的一方。在實際中,我們不必太過於究竟箭頭的方向,大多數情況下,關聯關係一般不強調關聯的方向。

多重性

關聯關係最明顯的特徵就是具有多重性,其意思是一個對象可能通過關聯關係鏈接到多個對象上。例如張三是員工類的對象,那麼張三很可能會通過與“工作任務類”之間的關聯,鏈接獲取到張三在“工作任務類”中存在的多個工作任務對象(設計XX、開發XX等),這當中對象通過關聯鏈接到數據量的“多少”即爲多重性的體現。常見的多重性包括:一對一關聯、一對多關聯、多對多關聯等,也可以是任意數量的多重性關聯,如*對*關聯(*代表任意數)。

多重性

例子

圖例

一對一

在某個教室中,一個學生只會指定一個座位,一個座位也只會安排一個學生。因此學生和座位之間是一對一的關聯關係。

一對多

在現實生活中,一個人可以選擇購買合法上牌的多輛汽車,而一輛合法上牌的車只屬於一個人。在這個場景中,人和車輛之間就屬於一對多的關聯關係。

多對多

在學校中,一名學生會學校多門課程(語數外),而一門課程(語文)會有多名學生學習。在這個場景中,學生和課程之間屬於多對多的關聯關係。

 

 讀圖檢查

在分析關聯關係的多重性是否合理時,可以通過“讀圖檢查法”來進行關聯關係的準確性判斷。你可以分別從左到右、從右到左來讀圖,看看有沒有不合理的地方。我們使用上面多重性表格中人和車的關係爲例,從左到右讀:一個人對應零到多個車。從右到左讀:一輛車對應一個人,而不能讀成:多輛車對應一個人。注意由“多”的一邊往另外一邊讀時,仍然是一個什麼對應多少個什麼,無論你從哪邊開始讀起,都是以“一個.....”開頭。

 

4.2.聚合關係

在分析出兩個類的關聯關係之後,兩個關聯的類中可能還存在一種整體和部分的含義,即存在整體包含部分的現象。對於這種,存在整體和部分含義的關聯關係可以進一步細化,表示成聚合關係。聚合關係可以看作,是在普通關聯的基礎上細化的一種特殊關聯關係。除了擁有關聯關係所有的基本特徵外,其中一個類描述了一個較大的事物(整體),另一個類代表較小的事物(部分),較小的事物可以構成一個較大的事物。

對於聚合關係的識別,可以在已有的關聯關係基礎上,通過分析兩個類之間是否存在:“整體由部分構成”、“部分是整體的一部分”等整體和部分的語義來完成。例如對於一個OA辦公系統來說,其中部門和員工之間的關聯關係就存在着整體和部分的含義。員工是部門的一部分,部門由員工構成。聚合關係是用一條帶空心菱形箭頭的直線表示,空心菱形箭頭指向的一端表示“整體”,反方向是“部分”。示例的聚合關係如下圖所示。

 

 

4.3.組合關係

組合關係是在聚合關係基礎之上延申的一種關聯關係,還可以將它看作是聚合關係的變體,或者是對聚合關係的進一步強調。因此組合關係具有關聯、聚合的所有特徵。在分析出聚合關係之後,還可以對針對整體和部分做進一步的分析:兩者之間除了整體擁有部分的語義之外,兩者之間是否屬於“強依賴”;並且整體和部分的生命週期是一致的。如果存在以上的特點,那麼可以將其表示爲組合關係。

“強依賴”和一致的生命週期意味着:整體擁有部分的同時,部分不能脫離整體而存在;當整體不存在時,部分也沒有存在的意義。對於組合關係中的整體和部分之間的關係特點,我們可以用一則成語來形象的描述:“皮之不存,毛將焉附”。在這種特點上,它和聚合關係恰恰相反,聚合關係即使整體不存在了,部分也依然存在。如果你認爲聚合和組合比較容易混淆,那麼你可以將聚合看成“弱包含關係”,組合可以看成“強包含關係”,以此來區分兩者之間的差異。

基於組合關係中整體和部分的“強依賴”現象,因此在圖中表示該關係的箭頭,是由一條實心菱形箭頭的直線表示,實心菱形箭頭指向的一端表示“整體”,反方向是“部分”。示例的組合關係如下圖所示。

 

 

4.4.依賴關係

概述

依賴關係是一種側重於“行爲”的使用關係,表示某個對象在某個場景下產生的行爲,需要使用另外一個對象提供的服務來完成。這也意味着,被使用對象的變化可能會影響到使用對象。依賴關係的分析要結合特定的場景和相應的行爲,這一點可表面它屬於一種臨時性的關係,它通常在行爲運行期產生,並且隨着運行場景的不同,依賴的對象也會發生變化。

臨時性

例如人和汽車這兩個對象,如果運行場景是讓汽車運行在馬路上,那麼汽車的運行則需要依賴於人的駕駛;如果場景變爲人乘坐汽車去上班,那就變成人上班通勤依賴於汽車的送達。可見,它並不像關聯關係那樣是一種天然的結構化關係,依賴關係是短暫的,它會隨着不同場景的變化而變化的,並且依賴關係是基於場景下的行爲所產生的,使用場景結束後,依賴關係也會暫時消失。如人和菜刀這兩個對象,靜態時它們沒有關係,但在廚房切菜的場景裏,人切菜的行爲就依賴於菜刀;脫離了這個切菜的場景,人就暫時不需要菜刀了。

運用

依賴關係的引用通常在代碼裏體現爲:類構造方法的參數、方法的參數。在分析時,如果發現A對象需要保存B對象的實例,但A對象的類中對B對象沒有操作,B發生修改後,A不會發生變化,僅僅是A“知道”B對象,那麼可以將其定義爲關聯關係。在分析時,如果發現A對象需要在某個業務場景的方法中,使用入參對象B的屬性或方法,那麼可以將其定義爲依賴關係,這同時也意味着,B的修改會導致A發生修改(A依賴於B)。依賴關係在圖中用一條帶箭頭的虛線表示,箭頭指向被依賴的對象。

 

 

4.4.泛化關係

泛化關係表明,一個類可以共享另外一個或多個類的結構和行爲。爲了實現泛化關係,我們引入了繼承機制,一個子類可以繼承一個或多個父類,子類會繼承父類的屬性、操作和關係,因此我們通常也將泛化稱爲繼承。此外,子類還可以根據自己的需要添加額外的屬性、操作或關係,還可以對父類已有的操作進行重新定義。其中,繼承一個父類爲單一繼承,繼承多個父類爲多重繼承。在實際的系統應用中,我們大多數採用單一繼承,因爲多重繼承會存在一些隱患問題,並且主流的編程語言(Java、C#)都不支持多重繼承。

泛化關係除了實現複用性,更深層次的目的是達到父類替代子類的可替換性,從而實現多態處理。另外,在分析出泛化關係後,可以通過描述類之間是否存在[is a 關係]或者[kind of 關係]的語義來驗證。具體來說,就是“子類是父類”(貓是動物),或“子類是父類的一種”(貓是動物的一種)。

泛化關係是用一條帶空心箭頭的直線表示。如下圖展示了學生管理系統的一種泛化關係,其中代表子類(畢業生類和新生類)都從父類(學生類)繼承,它們繼承了父類全部屬性和操作。此外,子類也會繼承父類中的關係,因此畢業生類和新生類於賬戶類也有聚合關係。


結語

UML類圖的學習並不是一蹴而就的,也不能指望看了幾篇教程就認爲自己會了。在學習初期階段先要保證自己能夠讀懂類圖,然後可以根據已有的業務分析結果“照葫蘆畫瓢”的繪製出來,最後關鍵的還是要在於通過實踐,根據具體業務發散出面向對象思想,並能將這個思想通過適當的方式在類圖中簡單明瞭的體現出來。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章