作者: 溫昱
摘要: UML是什么?是建模語(yǔ)言。本文就從語(yǔ)言和思維的關(guān)系談起,說(shuō)明UML對思維具有反作用——是促進(jìn)思維還是阻礙思維,全憑UML的使用者對UML內涵的掌握程度了。那么,如何達到“UML促進(jìn)思維”的境界呢?本文結合實(shí)例,說(shuō)明圖論思想在UML應用中的意義,希望能對讀者有所啟發(fā)。
人類(lèi)用詞匯表達一定的意義,這是件很有意思的事。比如,“模型”和“建模”這一對詞匯,形式上有一字相同,意義上也密切相關(guān);英文原詞model和modeling亦如此,形式上后者多了一個(gè)ing后綴;其實(shí),model和modeling詞源上根本就是同一個(gè)詞——model作動(dòng)詞時(shí)可以當“為……建模”講。
例子遠不止這些。心理學(xué)中,“語(yǔ)言”和“言語(yǔ)”關(guān)系緊密。“語(yǔ)言”是一種符號系統,由詞匯和語(yǔ)法構成。人們使用語(yǔ)言進(jìn)行思想交流,稱(chēng)為“言語(yǔ)”,它可以分為三種形式:口頭、書(shū)面、內部言語(yǔ)。心理學(xué)的研究表明:語(yǔ)言是思維的基礎,并對思維具有反作用;思維對事物的反映,總是借助語(yǔ)言進(jìn)行的;思維過(guò)程通過(guò)內部語(yǔ)言進(jìn)行,思維結果通過(guò)口頭或書(shū)面語(yǔ)言表現。
統一建模語(yǔ)言(Unified Modeling Language,UML)既然是一種語(yǔ)言,當然也會(huì )對思維有“反作用”——是促進(jìn)思維還是阻礙思維,全憑UML的使用者對UML內涵的掌握程度了。
本文結合實(shí)例,說(shuō)明圖論思想在UML應用中的意義。希望能對讀者達到“UML促進(jìn)其思維”的境界,帶來(lái)些許啟發(fā)。
一、圖的定義
顧名思義,圖論就是研究圖的理論。圖是一種由兩個(gè)集合——即一個(gè)頂點(diǎn)集合和一個(gè)邊集合——定義的抽象數據結構。圖的更形式化的定義如下:
稱(chēng)G=(V,E)是一個(gè)圖,如果
(1) V是一個(gè)非空有限集合,
(2) E是V中元素的無(wú)序對所組成的有限集合,
并把V的元素叫做圖的頂點(diǎn),E的元素叫做圖的邊。
舉個(gè)例子,下圖是一個(gè)有7個(gè)頂點(diǎn)和5條邊的圖,vi標出了頂點(diǎn),ei標出了邊。
二、圖的定義的UML應用——UML的圖論觀(guān)點(diǎn)
UML作為可視化建模語(yǔ)言,包括語(yǔ)法和語(yǔ)義兩個(gè)方面。單從語(yǔ)法方面,用圖論的眼光——把UML看作頂點(diǎn)和邊——來(lái)學(xué)習UML,應當說(shuō)是正本清源之道。下表以圖論觀(guān)點(diǎn)對UML語(yǔ)法進(jìn)行了總結。
頂點(diǎn)
邊
邊屬性
其它
用例圖
參與者, 用例
關(guān)聯(lián),泛化, 包含,擴展
接口
包圖
包, 接口
依賴(lài), 實(shí)現
可嵌入類(lèi)圖
類(lèi)圖
類(lèi)
關(guān)聯(lián), 泛化, 依賴(lài)
角色名,多重性,導航,組成符,聚集符,關(guān)聯(lián)名,關(guān)聯(lián)名方向
限定符, 參數化類(lèi), 關(guān)聯(lián)類(lèi)
對象圖
對象
鏈
角色名,多重性,導航,組成符,聚集符,鏈名,鏈名方向
順序圖
對象
消息
消息名,條件,重復
參與者實(shí)例,生命線(xiàn),激活
協(xié)作圖
對象
鏈,
消息
消息號,所有順序圖的邊(消息)屬性,所有對象圖的邊(鏈)屬性
參與者實(shí)例,位置,狀態(tài),變成流,拷貝流
構件圖
構件,接口
依賴(lài)
可嵌入對象圖
部署圖
節點(diǎn)
連接
可嵌入構件圖
狀態(tài)圖
狀態(tài)
轉換
條件,動(dòng)作
復合狀態(tài)
活動(dòng)圖
活動(dòng)狀態(tài)
完成轉換
條件,分支,分叉,結合
泳道,對象流
數學(xué)中,有關(guān)“數學(xué)抽象度”的研究表明:抽象層次越高,切近事物本質(zhì)越深。UML的圖論觀(guān)點(diǎn),從更抽象的“圖論”角度理解UML的語(yǔ)法,因此能夠“切近事物本質(zhì)更深”。UML 2.0即將全面到來(lái),改動(dòng)雖大,但決不會(huì )跳出圖論范疇;總之,理解了UML的圖論觀(guān)點(diǎn),對快速掌握UML新規范大有裨益,筆者的實(shí)踐也證明了這一點(diǎn)。
三、圖的定義的UML應用——關(guān)聯(lián)類(lèi)語(yǔ)法的理解
除了上面的基本總結以外,筆者發(fā)現UML中的關(guān)聯(lián)類(lèi)常被“誤用”或“該用不用”,所以有必要談一下。
語(yǔ)法方面,從圖論中對圖的基本定義,可以找到對關(guān)聯(lián)類(lèi)的“犀利”的理解。
首先,擴展一下圖論的“經(jīng)典”定義,如下圖所示。
擴展之后,頂點(diǎn)可以由更多的“角色”來(lái)承擔:除了通常的頂點(diǎn)外,邊也可以充當頂點(diǎn)。這樣以來(lái),邊就有如下三種情況:
l 連接頂點(diǎn)與頂點(diǎn)的邊
l 連接頂點(diǎn)與邊的邊
l 連接邊與邊的邊
然后,分析關(guān)聯(lián)類(lèi)本身的語(yǔ)法,它用到了上面擴展的第二種情況。如下圖所示,關(guān)聯(lián)類(lèi)語(yǔ)法分為關(guān)聯(lián)部分、類(lèi)部分、關(guān)聯(lián)部分和類(lèi)部分之間的可視化連接部分,共三部分內容。
總之,雖然從語(yǔ)義來(lái)講,關(guān)聯(lián)類(lèi)是一個(gè)獨立的模型元素,但從語(yǔ)法角度,它既包含了關(guān)聯(lián)的符號,又包含了類(lèi)的符號。
四、圖的定義的UML應用——說(shuō)說(shuō)序列圖
值得補充說(shuō)明的是,序列圖中“生命線(xiàn)”和“激活”也是可以充當頂點(diǎn)的邊,如下圖所示(該圖引用自《UML參考手冊》)。
五、有向邊
有向圖是無(wú)向圖的特殊情況,它們的定義有微妙的差異。
稱(chēng)G=(V,E)是一個(gè)有向圖,如果
(1) V是一個(gè)非空有限集合,
(2) E是V中元素的有序對所組成的有限集合,
并把V的元素叫做圖的頂點(diǎn),E的元素叫做圖的有向邊。
舉個(gè)例子,下圖是一個(gè)有向圖,描述的是足球賽的小組循環(huán)賽:a隊3場(chǎng)全勝,b、c、d這3個(gè)隊都是1勝2負。
六、有向邊的UML應用——依賴(lài)關(guān)系
靜態(tài)視圖是UML的基礎。模型中靜態(tài)視圖的元素是應用中有意義的概念,這些概念包括真實(shí)世界中的概念、抽象的概念、實(shí)現方面的概念和計算機領(lǐng)域的概念。靜態(tài)視圖中的關(guān)鍵元素是類(lèi)元及它們之間的關(guān)系;類(lèi)元是描述事物的建模元素,包括類(lèi)、接口和數據類(lèi)型等;類(lèi)元之間的關(guān)系有依賴(lài)、泛化、實(shí)現和關(guān)聯(lián)等。
依賴(lài)表示兩個(gè)或多個(gè)模型元素之間語(yǔ)義上的關(guān)系,即提供者的某些變化會(huì )要求或指示依賴(lài)關(guān)系中客戶(hù)的變化。例如,下圖體現了一條架構設計的基本原則:?jiǎn)?wèn)題領(lǐng)域層“不依賴(lài)于”其他任何層,而其他任何層“只依賴(lài)于”問(wèn)題領(lǐng)域層。
七、有向邊的UML應用——泛化、實(shí)現和關(guān)聯(lián)的依賴(lài)思想
根據依賴(lài)關(guān)系的定義——依賴(lài)表示兩個(gè)或多個(gè)模型元素之間語(yǔ)義上的關(guān)系,即提供者的某些變化會(huì )要求或指示依賴(lài)關(guān)系中客戶(hù)的變化——泛化、實(shí)現和關(guān)聯(lián)也是依賴(lài)關(guān)系,它們都包含了依賴(lài)的思想。
舉個(gè)例子。下面是一道極為常見(jiàn)的筆試題(以Java語(yǔ)言為例):
寫(xiě)出下列程序的運行結果:
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.");
}
}
這道題考的是構造函數的執行順序,輸出結果如下(以Java語(yǔ)言為例):
to construct Parent.
to construct Delegatee.
to construct Child.
其實(shí),構造函數的執行順序,何嘗不是“依賴(lài)思想”決定的呢?具體而言,就是“被依賴(lài)的先構造,依賴(lài)于人的后構造”。繼承關(guān)系包含了依賴(lài)思想,子類(lèi)Child依賴(lài)父類(lèi)Parent,所以Parent先于Child構造。關(guān)聯(lián)關(guān)系包含了依賴(lài)思想,聚集是關(guān)聯(lián)關(guān)系的一種,所以被聚集的類(lèi)Delegatee先于Child構造。
相信至今還有人會(huì )畫(huà)出類(lèi)似下面的類(lèi)圖,它錯在繼承的箭頭方向畫(huà)反了!經(jīng)過(guò)以上的討論,我們知道了大師們把繼承的箭頭方向規定為指向父類(lèi)有其深刻含義——它代表了依賴(lài)的方向!
八、有向邊的UML應用——一個(gè)例子
圖論是對現實(shí)世界中實(shí)際問(wèn)題的高度抽象。有向圖可以對具有特定依賴(lài)關(guān)系的實(shí)體群落,進(jìn)行有效的抽象刻畫(huà),使人們能夠忽略無(wú)關(guān)緊要的眾多細節,而牢牢把握住本質(zhì)性的依賴(lài)關(guān)系。
依賴(lài)關(guān)系在軟件開(kāi)發(fā)中的重要性,不管怎么強調都不過(guò)分。響應變化的能力往往決定一個(gè)項目的成敗,而依賴(lài)關(guān)系的處理(其實(shí)不僅包括類(lèi)與類(lèi)等工件之間的依賴(lài),還包括人之間的依賴(lài)和活動(dòng)之間的依賴(lài),本文不詳細討論)正是其中的關(guān)鍵。
著(zhù)名的開(kāi)放封閉原則(Open-Closed Principle,OCP)規定,“軟件實(shí)體應該是可以擴展的,但是不可修改”。本原則緊緊圍繞變化展開(kāi),變化來(lái)臨時(shí),如果不必改動(dòng)軟件實(shí)體的源代碼,就能擴充它的行為,那么這個(gè)軟件實(shí)體的設計就是滿(mǎn)足開(kāi)放封閉原則的。如果我們預測到某種變化,或者某種變化發(fā)生了,我們應當創(chuàng )建抽象來(lái)隔離以后發(fā)生的同類(lèi)變化。在Java中,這種抽象指抽象基類(lèi)或接口;在C++中,這種抽象是指抽象基類(lèi)或純抽象基類(lèi)。
比如,在開(kāi)發(fā)一個(gè)需求跟蹤工具的時(shí)候,起初可能僅需要支持保存為專(zhuān)有格式的“項目”文件,但后來(lái)又需要支持導出為HTML格式的網(wǎng)頁(yè)。讓我按照敏捷軟件開(kāi)發(fā)過(guò)程,來(lái)講述這個(gè)故事。
最開(kāi)始的設計如下圖所示,CReqMatrixDoc調用CProjectSaver來(lái)保存自己。按照開(kāi)放封閉原則(Open-Closed Principle),這并不是一個(gè)好的設計。但此時(shí),所有需求就是支持“保存為專(zhuān)有格式的項目文件”,而且我們并沒(méi)有預見(jiàn)到將來(lái)還需要以更多的形式保存,所以這個(gè)設計“不多不少”剛剛好。
后來(lái)需求發(fā)生了變化,這個(gè)工具需要支持“導出為HTML格式的網(wǎng)頁(yè)”的特性。是的,這個(gè)需求不管是客戶(hù)新提出來(lái)的,還是設計人員在上一個(gè)迭代有意忽略了,總之在這個(gè)迭代周期需求發(fā)生了變化。于是,設計人員意識到,需求跟蹤工具可能需要支持多種保存策略。
看來(lái),代碼出現了臭味(Smell),需要重構(Refactoring)。讓我們謹遵Martin Fowler“兩頂帽子”的教誨——不要將重構和添加新功能同時(shí)進(jìn)行——這一步我們僅進(jìn)行重構。我們要做的就是采用依賴(lài)倒置原則(Dependency-Inversion Principle)慣用的“用兩個(gè)抽象依賴(lài)代替一個(gè)具體依賴(lài)”策略,重構之后的設計如下圖所示。我們引入了一個(gè)接口CDocSaver,然后讓CProjectSaver實(shí)現這個(gè)接口。
哈,新的設計滿(mǎn)足開(kāi)放封閉原則,究其原因,最關(guān)鍵的一點(diǎn)就是CProjectSaver對CDocSaver接口的單向依賴(lài)。新設計非常易于擴充,我們只需新寫(xiě)一個(gè)CHtmlSaver來(lái)實(shí)現接口CDocSaver,就離支持“導出為HTML格式的網(wǎng)頁(yè)”不遠了,如下圖所示。咦,原來(lái)是GOF的Strategy模式。