作者: 溫昱
摘要: UML是什么?是建模語言。本文就從語言和思維的關(guān)系談起,說明UML對思維具有反作用——是促進(jìn)思維還是阻礙思維,全憑UML的使用者對UML內(nèi)涵的掌握程度了。那么,如何達(dá)到“UML促進(jìn)思維”的境界呢?本文結(jié)合實例,說明圖論思想在UML應(yīng)用中的意義,希望能對讀者有所啟發(fā)。
人類用詞匯表達(dá)一定的意義,這是件很有意思的事。比如,“模型”和“建模”這一對詞匯,形式上有一字相同,意義上也密切相關(guān);英文原詞model和modeling亦如此,形式上后者多了一個ing后綴;其實,model和modeling詞源上根本就是同一個詞——model作動詞時可以當(dāng)“為……建模”講。
例子遠(yuǎn)不止這些。心理學(xué)中,“語言”和“言語”關(guān)系緊密。“語言”是一種符號系統(tǒng),由詞匯和語法構(gòu)成。人們使用語言進(jìn)行思想交流,稱為“言語”,它可以分為三種形式:口頭、書面、內(nèi)部言語。心理學(xué)的研究表明:語言是思維的基礎(chǔ),并對思維具有反作用;思維對事物的反映,總是借助語言進(jìn)行的;思維過程通過內(nèi)部語言進(jìn)行,思維結(jié)果通過口頭或書面語言表現(xiàn)。
統(tǒng)一建模語言(Unified Modeling Language,UML)既然是一種語言,當(dāng)然也會對思維有“反作用”——是促進(jìn)思維還是阻礙思維,全憑UML的使用者對UML內(nèi)涵的掌握程度了。
本文結(jié)合實例,說明圖論思想在UML應(yīng)用中的意義。希望能對讀者達(dá)到“UML促進(jìn)其思維”的境界,帶來些許啟發(fā)。
一、圖的定義
顧名思義,圖論就是研究圖的理論。圖是一種由兩個集合——即一個頂點集合和一個邊集合——定義的抽象數(shù)據(jù)結(jié)構(gòu)。圖的更形式化的定義如下:
稱G=(V,E)是一個圖,如果
(1) V是一個非空有限集合,
(2) E是V中元素的無序?qū)λM成的有限集合,
并把V的元素叫做圖的頂點,E的元素叫做圖的邊。
舉個例子,下圖是一個有7個頂點和5條邊的圖,vi標(biāo)出了頂點,ei標(biāo)出了邊。
二、圖的定義的UML應(yīng)用——UML的圖論觀點
UML作為可視化建模語言,包括語法和語義兩個方面。單從語法方面,用圖論的眼光——把UML看作頂點和邊——來學(xué)習(xí)UML,應(yīng)當(dāng)說是正本清源之道。下表以圖論觀點對UML語法進(jìn)行了總結(jié)。
頂點
邊
邊屬性
其它
用例圖
參與者, 用例
關(guān)聯(lián),泛化, 包含,擴(kuò)展
接口
包圖
包, 接口
依賴, 實現(xiàn)
可嵌入類圖
類圖
類
關(guān)聯(lián), 泛化, 依賴
角色名,多重性,導(dǎo)航,組成符,聚集符,關(guān)聯(lián)名,關(guān)聯(lián)名方向
限定符, 參數(shù)化類, 關(guān)聯(lián)類
對象圖
對象
鏈
角色名,多重性,導(dǎo)航,組成符,聚集符,鏈名,鏈名方向
順序圖
對象
消息
消息名,條件,重復(fù)
參與者實例,生命線,激活
協(xié)作圖
對象
鏈,
消息
消息號,所有順序圖的邊(消息)屬性,所有對象圖的邊(鏈)屬性
參與者實例,位置,狀態(tài),變成流,拷貝流
構(gòu)件圖
構(gòu)件,接口
依賴
可嵌入對象圖
部署圖
節(jié)點
連接
可嵌入構(gòu)件圖
狀態(tài)圖
狀態(tài)
轉(zhuǎn)換
條件,動作
復(fù)合狀態(tài)
活動圖
活動狀態(tài)
完成轉(zhuǎn)換
條件,分支,分叉,結(jié)合
泳道,對象流
數(shù)學(xué)中,有關(guān)“數(shù)學(xué)抽象度”的研究表明:抽象層次越高,切近事物本質(zhì)越深。UML的圖論觀點,從更抽象的“圖論”角度理解UML的語法,因此能夠“切近事物本質(zhì)更深”。UML 2.0即將全面到來,改動雖大,但決不會跳出圖論范疇;總之,理解了UML的圖論觀點,對快速掌握UML新規(guī)范大有裨益,筆者的實踐也證明了這一點。
三、圖的定義的UML應(yīng)用——關(guān)聯(lián)類語法的理解
除了上面的基本總結(jié)以外,筆者發(fā)現(xiàn)UML中的關(guān)聯(lián)類常被“誤用”或“該用不用”,所以有必要談一下。
語法方面,從圖論中對圖的基本定義,可以找到對關(guān)聯(lián)類的“犀利”的理解。
首先,擴(kuò)展一下圖論的“經(jīng)典”定義,如下圖所示。
擴(kuò)展之后,頂點可以由更多的“角色”來承擔(dān):除了通常的頂點外,邊也可以充當(dāng)頂點。這樣以來,邊就有如下三種情況:
l 連接頂點與頂點的邊
l 連接頂點與邊的邊
l 連接邊與邊的邊
然后,分析關(guān)聯(lián)類本身的語法,它用到了上面擴(kuò)展的第二種情況。如下圖所示,關(guān)聯(lián)類語法分為關(guān)聯(lián)部分、類部分、關(guān)聯(lián)部分和類部分之間的可視化連接部分,共三部分內(nèi)容。
總之,雖然從語義來講,關(guān)聯(lián)類是一個獨立的模型元素,但從語法角度,它既包含了關(guān)聯(lián)的符號,又包含了類的符號。
四、圖的定義的UML應(yīng)用——說說序列圖
值得補(bǔ)充說明的是,序列圖中“生命線”和“激活”也是可以充當(dāng)頂點的邊,如下圖所示(該圖引用自《UML參考手冊》)。
五、有向邊
有向圖是無向圖的特殊情況,它們的定義有微妙的差異。
稱G=(V,E)是一個有向圖,如果
(1) V是一個非空有限集合,
(2) E是V中元素的有序?qū)λM成的有限集合,
并把V的元素叫做圖的頂點,E的元素叫做圖的有向邊。
舉個例子,下圖是一個有向圖,描述的是足球賽的小組循環(huán)賽:a隊3場全勝,b、c、d這3個隊都是1勝2負(fù)。
六、有向邊的UML應(yīng)用——依賴關(guān)系
靜態(tài)視圖是UML的基礎(chǔ)。模型中靜態(tài)視圖的元素是應(yīng)用中有意義的概念,這些概念包括真實世界中的概念、抽象的概念、實現(xiàn)方面的概念和計算機(jī)領(lǐng)域的概念。靜態(tài)視圖中的關(guān)鍵元素是類元及它們之間的關(guān)系;類元是描述事物的建模元素,包括類、接口和數(shù)據(jù)類型等;類元之間的關(guān)系有依賴、泛化、實現(xiàn)和關(guān)聯(lián)等。
依賴表示兩個或多個模型元素之間語義上的關(guān)系,即提供者的某些變化會要求或指示依賴關(guān)系中客戶的變化。例如,下圖體現(xiàn)了一條架構(gòu)設(shè)計的基本原則:問題領(lǐng)域?qū)?#8220;不依賴于”其他任何層,而其他任何層“只依賴于”問題領(lǐng)域?qū)印?div style="height:15px;">

七、有向邊的UML應(yīng)用——泛化、實現(xiàn)和關(guān)聯(lián)的依賴思想
根據(jù)依賴關(guān)系的定義——依賴表示兩個或多個模型元素之間語義上的關(guān)系,即提供者的某些變化會要求或指示依賴關(guān)系中客戶的變化——泛化、實現(xiàn)和關(guān)聯(lián)也是依賴關(guān)系,它們都包含了依賴的思想。
舉個例子。下面是一道極為常見的筆試題(以Java語言為例):
寫出下列程序的運(yùn)行結(jié)果:
public class Test {
public static void main(String[] args) {
Child child = new Child();
}
}
class Parent {
Parent() {
System.out.println("to construct Parent.");
}
}
class Child extends Parent {
Child() {
System.out.println("to construct Child.");
}
Delegatee delegatee = new Delegatee();
}
class Delegatee {
Delegatee() {
System.out.println("to construct Delegatee.");
}
}
這道題考的是構(gòu)造函數(shù)的執(zhí)行順序,輸出結(jié)果如下(以Java語言為例):
to construct Parent.
to construct Delegatee.
to construct Child.
其實,構(gòu)造函數(shù)的執(zhí)行順序,何嘗不是“依賴思想”決定的呢?具體而言,就是“被依賴的先構(gòu)造,依賴于人的后構(gòu)造”。繼承關(guān)系包含了依賴思想,子類Child依賴父類Parent,所以Parent先于Child構(gòu)造。關(guān)聯(lián)關(guān)系包含了依賴思想,聚集是關(guān)聯(lián)關(guān)系的一種,所以被聚集的類Delegatee先于Child構(gòu)造。
相信至今還有人會畫出類似下面的類圖,它錯在繼承的箭頭方向畫反了!經(jīng)過以上的討論,我們知道了大師們把繼承的箭頭方向規(guī)定為指向父類有其深刻含義——它代表了依賴的方向!
八、有向邊的UML應(yīng)用——一個例子
圖論是對現(xiàn)實世界中實際問題的高度抽象。有向圖可以對具有特定依賴關(guān)系的實體群落,進(jìn)行有效的抽象刻畫,使人們能夠忽略無關(guān)緊要的眾多細(xì)節(jié),而牢牢把握住本質(zhì)性的依賴關(guān)系。
依賴關(guān)系在軟件開發(fā)中的重要性,不管怎么強(qiáng)調(diào)都不過分。響應(yīng)變化的能力往往決定一個項目的成敗,而依賴關(guān)系的處理(其實不僅包括類與類等工件之間的依賴,還包括人之間的依賴和活動之間的依賴,本文不詳細(xì)討論)正是其中的關(guān)鍵。
著名的開放封閉原則(Open-Closed Principle,OCP)規(guī)定,“軟件實體應(yīng)該是可以擴(kuò)展的,但是不可修改”。本原則緊緊圍繞變化展開,變化來臨時,如果不必改動軟件實體的源代碼,就能擴(kuò)充它的行為,那么這個軟件實體的設(shè)計就是滿足開放封閉原則的。如果我們預(yù)測到某種變化,或者某種變化發(fā)生了,我們應(yīng)當(dāng)創(chuàng)建抽象來隔離以后發(fā)生的同類變化。在Java中,這種抽象指抽象基類或接口;在C++中,這種抽象是指抽象基類或純抽象基類。
比如,在開發(fā)一個需求跟蹤工具的時候,起初可能僅需要支持保存為專有格式的“項目”文件,但后來又需要支持導(dǎo)出為HTML格式的網(wǎng)頁。讓我按照敏捷軟件開發(fā)過程,來講述這個故事。
最開始的設(shè)計如下圖所示,CReqMatrixDoc調(diào)用CProjectSaver來保存自己。按照開放封閉原則(Open-Closed Principle),這并不是一個好的設(shè)計。但此時,所有需求就是支持“保存為專有格式的項目文件”,而且我們并沒有預(yù)見到將來還需要以更多的形式保存,所以這個設(shè)計“不多不少”剛剛好。
后來需求發(fā)生了變化,這個工具需要支持“導(dǎo)出為HTML格式的網(wǎng)頁”的特性。是的,這個需求不管是客戶新提出來的,還是設(shè)計人員在上一個迭代有意忽略了,總之在這個迭代周期需求發(fā)生了變化。于是,設(shè)計人員意識到,需求跟蹤工具可能需要支持多種保存策略。
看來,代碼出現(xiàn)了臭味(Smell),需要重構(gòu)(Refactoring)。讓我們謹(jǐn)遵Martin Fowler“兩頂帽子”的教誨——不要將重構(gòu)和添加新功能同時進(jìn)行——這一步我們僅進(jìn)行重構(gòu)。我們要做的就是采用依賴倒置原則(Dependency-Inversion Principle)慣用的“用兩個抽象依賴代替一個具體依賴”策略,重構(gòu)之后的設(shè)計如下圖所示。我們引入了一個接口CDocSaver,然后讓CProjectSaver實現(xiàn)這個接口。
哈,新的設(shè)計滿足開放封閉原則,究其原因,最關(guān)鍵的一點就是CProjectSaver對CDocSaver接口的單向依賴。新設(shè)計非常易于擴(kuò)充,我們只需新寫一個CHtmlSaver來實現(xiàn)接口CDocSaver,就離支持“導(dǎo)出為HTML格式的網(wǎng)頁”不遠(yuǎn)了,如下圖所示。咦,原來是GOF的Strategy模式。