談談UML中類圖之間的關係

我們在用UML建模語言做軟件設計的時候,通常會用到UML的類圖,而這個類圖之間的關係很多時候卻給大家帶來了不必要的麻煩和疑惑,尤其是對於組合與聚合等這樣的關係不太好把握,在這裏我談一下我的理解和應用。

UML中將類與類之間的關係細分爲這樣幾種關係,分別是: 關聯、聚合/組合、依賴、泛化(繼承)。正因爲這樣的細分,導致我們在使用的時候,顯得比較迷茫,並且由於在做UML時,不可避免的與某一種開發語言進行聯繫,更加導致了對類圖之間的關係不好把握了。比如對於組合與聚合之間的關係,主要表現爲一個對象是否對其他對象進行生命週期的管理,在C++中,我們需要人爲的進行對象生命週期的管理,而在JAVA語言中,卻因爲存在於JAVA虛擬機,而虛擬機可以幫我們管理對象的生命週期,所以如果是JAVA的設計人員來進行類圖關係設計時,就會更加對組合與聚合的關係把握不準了。

其實我們在做類之間的關係時,往往忽略了一點,就是類本身就是抽象意義上的概念,而所謂的類之間的關係,通常是爲了表述在類的實例化對象時,對象之間的關係。所以會給人覺得,類的關係到底是指的是類之間的關係還是指的是類的實例化對象之間的關係。對於這樣的問題,我覺得既然是指的是類之間的關係,就不存在實例化對象之間的關係。只要把類之間的關係把握好了,那麼實例化的對象之間的關係自然而然地形成。

我個人的理解是這樣的,類的關係只有兩種,就是橫向與縱向的關係。所謂橫向指的是如關聯,組合/聚合,依賴這四種關係,他們看起來更像是橫向對比的概念上引出的,而縱向指的是繼承與實現接口(以及實現抽象類)這樣的關係。

下面先說說縱向關係的類圖。

縱向關係的類關係含有三種類型,分別是繼承關係,實現接口關係以及實現抽象類關係,在不同的語言中,其語言的使用規則不一樣,但是從類這個抽象意義的概念來說,是沒有什麼分別的,繼承關係指的是子類擁有父類所允許的一些特定的屬性和行爲,在面嚮對象語言中,可能很多人會覺得繼承很利害,其實在實際開發中,我們卻很少見到那麼多的繼承關係在用的,爲什麼呢?這裏拋開那些經常所說的多重繼承帶來的問題不說,其實就我的理解,所謂繼承能給我們帶來的唯一好處是我們可以少寫一些代碼而已,可是這個少寫代碼的好處在實際中真正的並不能帶來什麼效率的提高,因爲你想少寫代碼,勢必會在父類寫很多東西,而這個卻很可能造成巨類的後果,並且有可能違背了一個類只做與它相關事情的原則。所以繼承關係在面嚮對象語言裏面雖然很重要,但是實際使用中不必要強行做繼承關係。下面爲繼承關係的類圖:

從繼承關係我們也可以看出,類與類之間的縱向關係從樣子上看起來,就是一個類在另一個類的上面(當然這樣說很牽強,但我們在畫圖時,儘量這樣去表現,可以一目瞭然),而橫向關係,則通常是兩者具有同等位置的關係,這也表現了一種平等的關係,而縱向關係是不平等的。

對於縱向關係的其他兩種類型,接口實現和繼承抽象類,由於不同的做圖工具,其表現的樣式不一樣,這個就不好畫圖了,我的建議是,接口實現和繼承抽象類也用上面的一般繼承關係的畫法,只是需要將這幾者區別開來,那麼怎麼區別呢?有些做圖工具的區別是兩個類之間的聯繫圖上做手腳,有些圖是在父類上加上一些特殊的標識,我覺得最好的辦法是就在旁邊加上一個文本註釋最好,簡單明瞭,一目瞭然,示例如下:

 

這樣讓閱讀類圖的人一看就明白是怎麼回事了。可能有些人會有疑問,這樣與UML的標準不一樣,可是我要說的是,有時需要靈活變通一下,在UML未出現以前,我們不會用這樣的標準來做軟件設計,可是我們通常會在討論設計時,畫一些圖出來,而那些圖不是UML標準,但是對於討論者和閱讀者都能明白,而這纔是我們的目的,不要拘泥於形式上上的東西,我們學UML,用UML,無非是想讓它更好地爲我們的設計服務,而不是爲了畫出漂亮的UML圖。

縱向關係的繼承抽象類畫法同上面所述,這裏不再綴述。在做縱向關係時,關鍵要明白的是哪些要做成基類,哪些要做成接口,而哪些要做成抽象類,這個在UML之外,與具體的業務相關了。

現在說說橫向關係。橫向關係含有四種類型,分別是關聯,依賴,組合,聚合,很多時候,我們並不能很好地區分這四種關係,爲什麼?因爲針對不同的開發語言,其表現形式是不一樣的,比如針對依賴關係來說,在C++語言中,通過頭文件,函數參數來加以區別,而在JAVA中,頭文件的概念沒有,而是通過包的形式來表現。還有組合與聚合,通過對對象的生命週期的管理來判定,在象JAVA語言而言,其自動垃圾回收機制,使我們很多時候沒有考慮到對象的生命週期結束的問題,所以對組合與聚合就更加模糊了。有一種區別方法是通過業務模型來區別,這種方法比直接與開發語言關聯講解要好些,但是有一個問題是業務領域的分析,由於過於抽象,不一定別人能夠完全明白是怎麼回事。這使我們想到了如果不採用UML這種工具時,在做設計時,我們仍然能夠很好地通過參與人員自行定義的類型進行設計和講解,從這個角度來說,不採用UML我們一樣能做設計,那麼如果我們要強行地進行組合與聚合的區別,則太過於注重表現形式,而忽略了我們採用UML這種技術主要是用來進行設計和交流的,只要設計人員和開發人員能夠通過圖一目瞭然,這纔是最有效的方式。

說到這裏,我所想要表達的意思是,我們不必要去區別於組合,聚合到底有什麼區別,那只是形式而已,我們更應試關注的是實質性的東西,即我們到底該如何用好UML這個技術,給我們帶來更快的設計和交流。這又回到了起點了,即不管他們的表現形式如何,其實都是一種橫向關聯,有些人把組合與聚合看成是關聯關係的一種特殊情況,即含有一對多時的關係爲組合或者聚合,而一對一的爲關聯關係,這個看起來也是沒有錯的。不過我覺得沒有必要這樣區別,我們可以認爲都是關聯關係,至於如果出現一對多的情況,則可以在旁邊加上註釋說明,則簡單明瞭,也不用去給很多不太明白UML技術的人講解含有實心棱形的是聚合,含有空心棱形的是組合(講解起來實在是太累了)。下圖爲實際使用時的註釋,通過在註釋中加入說明性的文字,可以很容易明白二者的關係。

 

通過上面的方式,可以很好的迴避關聯關係與組合,聚合關係等的複雜性,使得設計出來的類圖關係清晰,易懂。

現在還有一種關係需要說明,就是依賴關係,在很多人講解的關係說明中,通過加入頭文件,是否是類屬性還是函數參數等加以區別,可是由於開發語言的語法不同,表現形式也有所區別,這也仍然犯了只流於表面形式的錯誤。在實際情況下,我們沒有必要去關係到底是關聯關係還是依賴關係,因爲不管是哪種關係,都會告訴我們一個基本的事情,我們需要其他類的對象來實現本類對象所要做的事情,這個纔是最根本的。不管是作爲類的成員屬性還是函數參數,那只是表面現象而已,我們更應該關注的是,我們依賴於另一個類,是因爲要想完成本類所要做的事情,我們需要其他類的協助,這也是我把它們統一稱爲橫向關係的原因所在。

到這裏,應該明白類之間的這兩種關係的區別了,縱向關係是指的是不依賴於其他類對象,而橫向關係則依賴於其他類對象。還需要注意的是,橫向關係中,我偏向於將所有的關係描述爲依賴關係,而不是關聯關係,這個從現實的角度來說,更加合理一些,比如人與人之間發生聯繫時,通常是某人依賴於另外的人的存在,而簡單地描述爲關聯關係,不夠貼切,並且既然發生了聯繫,則必須是我們需要對方提供協助,這就是依賴於對方的協助。而在有些技術中提到了依賴注入,其中的依賴兩個字的意思其實就是指的這裏的關係表現,即類與類對象之間的依賴關係。而依賴注入的注入是什麼意思,這個我會在後面講解一下。

好了,最後要說明一點就是,我們在做設計是,對於類與類之間的關係,只要明白了橫向與縱向之間的區別即可,在畫UML類圖時,不必太過於關注UML本身的語法,因爲我們畫類圖的目的是一方面便於我們自己表達,另一方面是讓其他設計者或者開發人員更快更容易的明白,複雜的圖形只會加重閱讀者和交流者的心理負擔,而不會帶來明顯的好處。同時,在畫UML類圖時,我們是否在設計階段時,把所有的類都定義好,畫出來呢?這個問題不好回答,我舉一個我在實際項目中應用的例子,希望能夠說明問題。我曾經在做一個複雜系統時,在做類的設計時,當時希望所做的設計足夠詳細,這樣使開發人員能夠很快地完成代碼的編寫,但是在實際的開發中,我發現,代碼的編寫總不能很好地按照最初的設計進行,主要原因是你的設計中或多或少的有所欠缺,不可能很完美,以及在適應後期的需求變更時,可能也會影響最初的類設計。這樣一來,在後期的代碼開發中,如果出現和最初的類圖有差別,則需要回來維護以前的類圖。這裏可能帶來的一個壞處是由於之前把所有相關的類都考慮到了,如果再修改,可能會有牽一髮而動全身的危險。導致誰也不敢去維護類圖了。所以我在後面的使用中,只對關鍵的類關係進行類圖的整理與維護,這樣在後期維護中,很容易,而對於一些簡單的類,只需要在文檔中進行說明即可。

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