如今是二月,並且到現在你或許已經讀到、或聽到人們談論UML 2.0 —— 包括若干進步的 UML 的新規範,所作的變化。考慮到新規範的重要性,咱們也正在修改這個文章系列的基礎,把咱們的注意力從 OMG 的 UML 1.4 規範,轉移到 OMG 的已採納 UML 2.0草案規範(又名 UML 2)。我不喜歡在一系列文章的中間,把重點從 1.4 變爲 2.0 ,可是 UML 2.0 草案規範是前進的重要一步,我感受須要擴充文字。編程
因爲一些理由,OMG 改良了 UML 。主要的理由是,他們但願 UML 模型可以表達模型驅動架構(MDA),這意味着 UML 必須支持更多的模型驅動的符號。同時, UML 1.x 符號集合有時難以適用於較大的應用程序。此外,爲了要使圖變成更容易閱讀,須要改良符號元件。(舉例來講,UML 1.x 的模型邏輯流程太複雜,有時不可能完成。對UML 2 中的序列圖的符號集合的改變,已經在序列化邏輯建模方面取得巨大的進步)。架構
注意我上面所述的文字:「已採納UML2.0草案規範。」確實,規範仍然處於草案狀態,可是關鍵 是草案規範已經被 OMG 採用,OMG是一個直到新標準至關可靠,纔會採用它們的組織。 在 UML 2 徹底地被採用以前,規範將會有一些修改,可是這些改變應該是極小的。主要的改變將會是在 UML 的內部 —— 包括一般被實施 UML 工具的軟件公司使用的功能。併發
本文的主要目的是繼續把咱們的重點放在基礎UML圖上;這個月,咱們進一步瞭解序列圖。再次請注意,下面提供的例子正是以新的 UML 2 規範爲基礎。框架
圖的目的
序 列圖主要用於按照交互發生的一系列順序,顯示對象之間的這些交互。很象類圖,開發者通常認爲序列圖只對他們有意義。然而,一個組織的業務人員會發現,序列 圖顯示不一樣的業務對象如何交互,對於交流當前業務如何進行頗有用。除記錄組織的當前事件外,一個業務級的序列圖能被看成一個需求文件使用,爲實現一個將來 系統傳遞需求。在項目的需求階段,分析師能經過提供一個更加正式層次的表達,把用例帶入下一層次。那種狀況下,用例經常被細化爲一個或者更多的序列圖。異步
組織的技術人員能發現,序列圖在記錄一個將來系統的行爲應該如何表現中,很是有用。在設計階段,架構師和開發者能使用圖,挖掘出系統對象間的交互,這樣充實整個系統設計。編程語言
序列圖的主要用途之一,是把用例表達的需求,轉化爲進一步、更加正式層次的精細表達。用例經常被 細化爲一個或者更多的序列圖。序列圖除了在設計新系統方面的用途外,它們還能用來記錄一個存在系統(稱它爲「遺產」)的對象如今如何交互。當把這個系統移 交給另外一我的或組織時,這個文檔頗有用。工具
符號
既 然這是我基於 UML 2的 UML 圖系列文章的第一篇,咱們須要首先討論對 UML 2 圖符號的一個補充,即一個叫作框架的符號元件。在 UML 2中,框架元件用於做爲許多其餘的圖元件的一個基礎,可是大多數人第一次接觸框架元件的狀況,是做爲圖的圖形化邊界。當爲圖提供圖形化邊界時,一個框架元 件爲圖的標籤提供一致的位置。在 UML 圖中框架元件是可選擇的;就如你能在圖 1 和 2 中見到的,圖的標籤被放在左上角,在我將調用框架的「namebox」中,一種卷角長方形,並且實際的 UML 圖在較大的封閉長方形內部定義。oop
除了提供一個圖形化邊框以外,用於圖中的框架元件也有描述交互的重要的功能, 例如序列圖。在序列圖上一個序列接收和發送消息(又稱交互),能經過鏈接消息和框架元件邊界,創建模型(如圖 2 所見到)。這將會在後面「超越基礎」的段落中被更詳細地介紹。spa
注意在圖 2 中,對於序列圖,圖的標籤由文字「sd」開始。當使用一個框架元件封閉一個圖時,圖的標籤須要按照如下的格式:
圖類型 圖名稱
UML 規範給圖類型提供特定的文本值。(舉例來講,sd表明序列圖,activity表明活動圖,use case表明用例圖)。
基礎
序 列圖的主要目的是定義事件序列,產生一些但願的輸出。重點不是消息自己,而是消息產生的順序;不過,大多數序列圖會表示一個系統的對象之間傳遞的什麼消 息,以及它們發生的順序。圖按照水平和垂直的維度傳遞信息:垂直維度從上而下表示消息/調用發生的時間序列,並且水平維度從左到右表示消息發送到的對象實 例。
生命線
當畫一個序列圖的時候,放置生命線符號元件,橫跨圖的頂部。生命線表示序列中,建模的角色或對象實例。 1 生命線畫做一個方格,一條虛線從上而下,經過底部邊界的中心(圖 3)。生命線名字放置在方格里。
圖 3: 用於一個實體名爲freshman的生命線的Student類的一個例子
UML 的生命線命名標準按照以下格式:
實體名 : 類名
在如圖3所示的例子中,生命線表示類Student的實體,它的實體名稱是freshman。這 裏注意一點,生命線名稱帶下劃線。當使用下劃線時,意味着序列圖中的生命線表明一個類的特定實體,不是特定種類的實體(例如,角色)。在未來的一篇文章 中,咱們將會了解結構化建模。如今,僅僅評述序列圖,可能包含角色(例如買方 和賣方 ),而不須要敘述誰扮演那些角色(例如Bill 和Fred )。這准許不一樣語境的圖重複使用。簡單拖放,序列圖的實例名稱有下劃線,而角色名稱沒有。
圖 3 中咱們生命線例子是一個命名的對象,可是不是全部的生命線都表明命名的對象。相反的,一個生命線能用來表現一個匿名的或未命名的實體。當在一個序列圖上, 爲一個未命名的實例建模時,生命線的名字採用和一個命名實例相同的模式;可是生命線名字的位置留下空白,而不是提供一個例圖名字。再次參考圖 3,若是生命線正在表現Student類的一個匿名例圖,生命線會是: 「Student」。同時, 由於序列圖在項目設計階段中使用,有一個未指定的對象是徹底合法: 舉例來講,「freshman」。
消息
爲了可讀性,序列圖的第一個消息老是從頂端開始,而且通常位於圖的左邊。而後繼發的消息加入圖中,稍微比前面的消息低些。
爲了顯示一個對象(例如,生命線)傳遞一個消息給另一個對象,你畫一條線指向接收對象,包括一 個實心箭頭(若是是一個同步調用操做)或一個棍形箭頭(若是是一個異步訊號)。消息/方法名字放置在帶箭頭的線上面。正在被傳遞給接收對象的消息,表示接 收對象的類實現的一個操做/方法。在圖 4 的例子中,analyst對象調用ReportingSystem 類的一個實例的系統對象。analyst對象在調用系統對象的 getAvailableReports 方法。系統對象而後調用secSystem 對象上的、包括參數userId的getSecurityClearance 方法,secSystem的類的類型是 SecuritySystem。 2
除了僅僅顯示序列圖上的消息調用外,圖 4 中的圖還包括返回消息。這些返回消息是可選擇的;一個返回消息畫做一個帶開放箭頭的虛線,向後指向來源的生命線,在這條虛線上面,你放置操做的返回值。在 圖 4 中,當 getSecurityClearance 方法被調用時,secSystem 對象返回 userClearance 給系統對象。當 getAvailableReports 方法被調用時,系統對象返回 availableReports。
此外,返回消息是序列圖的一個可選擇部分。返回消息的使用依賴建模的具體/抽象程度。若是須要較好的具體化,返回消息是有用的;不然,主動消息就足夠了。我我的喜歡,不管何時返回一個值,都包括一個返回消息,由於我發現額外的細節使一個序列圖變得更容易閱讀。
當序列圖建模時,有時候,一個對象將會須要傳遞一個消息給它自己。一個對象什麼時候稱它自己?一個純 化論者會爭辯一個對象應該永不傳遞一個消息給它自己。然而,爲傳遞一個消息給它自己的對象建模,在一些情境中多是有用的。舉例來講,圖 5 是圖 4 的一個改良版本。 圖 5 版本顯示調用它的 determineAvailableReports 方法的系統對象。經過表示系統傳遞消息「determineAvailableReports」給它自己,模型把注意力集中到過程的事實上,而不是系統對 象。
爲了要畫一個調用自己的對象,如你平時所做的,畫一條消息,可是不是鏈接它到另外的一個對象,而是你把消息鏈接回對象自己。
圖 5: 系統對象調用它的 determineAvailableReports 方法
圖 5 中的消息實例顯示同步消息;然而,在序列圖中,你也能爲異步消息建模。一個異步消息和一個同步的畫法相似,可是消息畫的線帶一個棍形矛頭,如圖 6 所示。
約束
當 爲對象的交互建模時,有時候,必須知足一個條件,消息纔會傳遞給對象。約束在 UML 圖各處中,用於控制流。在這裏,我將會討論UML 1.x 及UML 2.0二者的約束。在 UML 1.x 中,一個約束只可能被分配到一個單一消息。UML 1.x中,爲了在一個序列圖上畫一個約束,你把約束元件放在約束的消息線上,消息名字以前。圖 7 顯示序列圖的一個片斷,消息addStudent 方法上有一個約束。
圖 7:UML 1.x 序列圖的一個片斷,其中addStudent 消息有一個約束
在圖 7 中,約束是文本「[ pastDueBalance=0]」。經過這個消息上的約束,若是應收賬系統返回一個零點的逾期平衡,addStudent 消息纔將會被傳遞。約束的符號很簡單;格式是:
[Boolean Test]
舉例來講,
[pastDueBalance = 0]
組合碎片(變體方案,選擇項,和循環)
然 而,在大多數的序列圖中,UML 1.x「in-line」約束不足以處理一個建模序列的必需邏輯。這個功能缺失是 UML 1.x 的一個問題。UML 2 已經經過去掉「in-line」約束,增長一個叫作組合碎片的符號元件,解決了這一個問題。一個組合碎片用來把一套消息組合在一塊兒,在一個序列圖中顯示條 件分支。UML 2 規範指明瞭組合碎片的 11 種交互類型。十一種中的三種將會在「基礎」段落中介紹,另外兩種類型將會在「超越基礎」中介紹,而那剩餘的六種我將會留在另外一篇文章中介紹。(嗨,這是一 篇文章而不是一本書。我但願你在一天中看完這部分!)
變體
變體用來指明在兩個或更多的消息序列之間的、互斥的選擇。 3 變體支持經典的「if then else」邏輯的建模(舉例來講,若是 我買三個,而後 我獲得 我購買的20% 折扣;不然 我獲得我購買的 10% 折扣)。
就如你將會在圖 8 中注意到的,一個變體的組合碎片元件使用框架來畫。單詞「alt」放置在框架的namebox裏。而後較大的長方形分爲 UML 2 所稱的操做元。 4 操做元被虛線分開。每一個操做元有一個約束進行測試,而這個約束被放置在生命線頂端的操做元的左上部。 5 若是操做元的約束等於「true」,而後那個操做元是要執行的操做元。
圖 8做爲一個變體的組合碎片如何閱讀的例子,顯示序列從頂部開始,即bank對象獲取支票金額和賬戶結餘。此時,序列圖中的變體組合碎片接管。由於約束 「[balance >= amount]」,若是餘額超過或等於金額,而後順序進行bank對象傳遞 addDebitTransaction 和 storePhotoOfCheck 消息給account對象。然而,若是餘額不是超過或等於金額,而後順序的過程就是bank傳遞addInsuffientFundFee 和 noteReturnedCheck 消息給account對象,returnCheck 消息給它自身。由於「else」約束,當餘額不大於或者等於金額時,第二個序列被調用。在變體的組合碎片中,不須要「else」約束;而若是一個操做元, 在它上面沒有一個明確的約束,那麼將假定「else」約束。
變體的組合碎片沒被限制在簡單的「if then else」驗證。可能須要大量的變體路徑。 若是須要較多的變體方案,你必定要作的所有工做就是把一個操做元加入有序列約束和消息的長方形中。
選擇項
選擇項組合碎片用來爲序列建模,這些序列給予一個特定條件,將會發生的;或者,序列不發生。一個選擇項用來爲簡單的「if then」表達式建模。(例如,若是架上的圈餅少於五個,那麼另外作兩打圈餅)。
選擇項組合碎片符號與變體組合碎片相似,除了它只有一個操做元而且永不能有「else」約束之外 (它就是如此,沒有理由)。要畫選擇項組合,你畫一個框架。文字「opt」是被放置在框架的 namebox 裏的文本,在框架的內容區,選擇項的約束被放置在生命線頂端上的左上角。 而後選擇項的消息序列被放在框架的內容區的其他位置內。這些元件如圖 9 所示。
閱讀選擇項組合碎片很容易。圖 9 是圖 7 的序列圖片斷的再加工,可是此次它使用一個選擇項組合碎片,由於若是Student的逾期平衡等於0,須要傳遞更多的消息。按照圖 9 的序列圖,若是Student的逾期平衡等於零,而後傳遞addStudent,getCostOfClass和chargeForClass消息H綣鸖 tudent的逾期平衡不等於零,那麼在選擇項組合碎片中,序列不傳遞任何一個消息。
例子圖 9的序列圖片斷包括一個選擇項約束;然而,約束不是一個必需的元件。在高層次、抽象的序列圖中,你可能不想敘述選擇項的條件。你可能只是想要指出片斷是可選擇的。
循環
有時候你將會須要爲一個重複的序列建模。在 UML 2 中,爲一個重複的序列建模已經改良,附加了循環組合碎片。
循環組合碎片表面很是相似選擇項組合碎片。你畫一個框架,在框架的 namebox 中放置文本「loop」。在框架的內容區中,一個生命線的頂部,循環約束 6被 放置在左上角。而後循環的消息序列被放在框架內容區的其他部分中。在一個循環中,除了標準的布爾測試外,一個約束能測試二個特定的條件式。特定的約束條件 式是寫做「minint = [the number]」(例如,「minint = 1」)的最小循環次數,和寫做「maxint = [the number]」(例如,「maxint = 5」)的最大循環次數。經過最小循環檢驗,循環必須運行至少指定次數,而循環執行次數不能達到約束指定的最大循環次數。
在圖 10 中顯示的循環運行,直到 reportsEnu 對象的 hasAnotherReport 消息返回false。若是循環序列應該運行,這個序列圖的循環使用一個布爾測試確認。爲了閱讀這個圖,你和日常同樣,從頂部開始。當你到達循環組合碎片, 作一個測試,看看值 hasAnotherReport 是否等於true。若是 hasAnotherReport 值等於true,因而序列進入循環片段。而後你能和正常狀況同樣,在序列圖中跟蹤循環的消息。
我已經介紹了序列圖的基礎,應該使你能夠爲將會在系統中一般發生的大部份交互建模。下面段落將會介紹用於序列圖的比較高階的符號元件。
引用另一個序列圖
當作序列圖的時候,開發者愛在他們的序列圖中,重用存在的序列圖。 7 在 UML 2 中開始,引進「交互進行」元件。追加交互進行的能夠說是 UML 2 交互建模中的最重要的創新。交互進行增長了功能,把原始的序列圖組織成爲複雜的序列圖。因爲這些,你能組合(重用)較簡單的序列,生成比較複雜的序列。這 意味你能把完整的、可能比較複雜的序列,抽象爲一個單一的概念單位。
一個交互進行元件使用一個框架繪製。文字「ref」放置在框架的 namebox 中,引用的序列圖名字放置在框架的內容區裏,連同序列圖的任何參數一塊兒。引用序列圖的名字符號以下模式:
序列圖名[(參數)] [: 返回值]
兩個例子:
1.
Retrieve Borrower Credit Report(ssn) : borrowerCreditReport
或者
2.
Process Credit Card(name, number, expirationDate, amount : 100)
在例子 1 中,語法調用叫作Retrieve Borrower Credit Report的序列圖,傳遞給它參數 ssn。序列Retreive Borrower Credit Report返回變量 borrowerCreditReport 。
在實例 2 中,語法調用叫作Process Credit Card的序列圖,傳遞給它參數name,number,expiration date,和 amount。然而,在例子 2 中,amount參數將會是值100。由於例子2沒有返回值標籤,序列不返回值(假設,建模的序列不須要返回值)。
圖 11 顯示一個序列圖,它引用了序列圖「Balance Lookup」和「Debit Account」。序列從左上角開始,客戶傳遞一個消息給teller對象。teller對象傳遞一個消息給 theirBank 對象。那時,調用Balance Lookup序列圖,而 accountNumber做爲一個參數傳遞。Balance Lookup序列圖返回balance變量。而後檢驗選擇項組合碎片的約束條件,確認餘額大於金額變量。在餘額比金額更大的狀況下,調用Debit Account序列圖,給它傳遞參數accountNumber 和amount。在那個序列完成後,withdrawCash 消息爲客戶返回cash。
重要的是,注意在圖 11 中,theirBank 的生命線被交互進行Balance Lookup隱藏了。由於交互進行隱藏生命線,意味着theirBank 生命線在「Balance Lookup」序列圖中被引用。除了隱藏交互進行的生命線以外,UML 2 也指明,生命線在它本身的「Balance Lookup」序列中,必定有相同的 theirBank 。
有時候,你爲一個序列圖建模,其中交互進行會重疊沒有 在交互進行中引用的生命線。在那種狀況下,生命線和正常的生命線同樣顯示,不會被重疊的交互進行隱藏。
在圖 11 中,序列引用「Balance Lookup」序列圖。「Balance Lookup」序列圖在圖 12 中顯示。由於例子序列有參數和一個返回值,它的標籤 —— 位於圖的 namebox 中 —— 按照一個特定模式:
圖類型 圖名 [參數類型:參數名]
[: 返回值類型]
兩個例子:
1.
SD Balance Lookup(Integer : accountNumber) : Real
或
2.
SD Available Reports(Financial Analyst : analyst) : Reports
圖 12 舉例說明例子 1,在裏面,Balance Lookup序列把參數 accountNumber 做爲序列中的變量使用,序列圖顯示返回的Real對象。在相似這種狀況下,返回的對象採用序列圖實體名。
圖 12: 一個使用 accountNumber 參數並返回一個Real對象的序列圖
圖 13 舉例說明例子 2,在裏面,一個序列圖獲取一個參數,返回一個對象。然而,在圖 13 中參數在序列的交互中使用。
圖 13: 一個在它的交互中使用參數、返回一個Reports對象的序列圖
門
前 面的段落展現如何經過參數和返回值傳遞信息,引用另外一個序列圖。然而,有另外一個方法在序列圖之間傳遞消息。門多是一個容易的方法,爲在序列圖和它的上下 文之間的傳遞消息建模。一個門只是一個消息,圖形表示爲一端鏈接序列圖的框架邊緣,另外一端鏈接到生命線。使用門的圖 11 和 12 ,在圖 14 和 15 中能夠被看到重構。圖 15 的例圖有一個叫作getBalance的入口門,獲取參數 accountNumber。由於是箭頭的線鏈接到圖的框架,而箭頭鏈接到生命線,因此 getBalance 消息是一個入口門。序列圖也有一個出囗門,返回balance變量。出口門同理可知,由於它是一個返回消息,鏈接從一個生命線到圖的框架,箭頭鏈接框架。
組合碎片(跳轉和並行)
在本文前面「基礎」的段落中呈現的,我介紹了「變體」,「選擇項」,和「循環」的組合碎片。這些三個組合碎片是大多數人將會使用最多的。然而,有二個其餘的組合碎片,大量共享的人將會發現有用——跳轉和並行。
跳轉
跳 轉組合碎片幾乎在每一個方面都和選擇項組合碎片一致,除了兩個例外。首先,跳轉的框架namebox的文本「break」代替了「option」。其次, 當一個跳轉組合碎片的消息運行時,封閉的交互做用的其餘消息將不會執行,由於序列打破了封閉的交互。這樣,跳轉組合碎片很是象 C++ 或 Java 的編程語言中的break關鍵字。
圖 16: 來自圖 8 的序列圖片斷的重構,片斷使用跳轉代替變體
跳轉最經常使用來作模型異常處理。圖 16 是圖 8 的重構,可是此次圖16使用跳轉組合碎片,由於它把balance < amount的狀況做爲一個異常對待,而不是一個變體流。要閱讀圖 16,你從序列的左上角開始,向下讀。當序列到達返回值「balance」的時候,它檢查看看是否餘額比金額更少。若是餘額很多於金額,被傳遞的下一個消 息是 addDebitTransaction 消息,並且序列正常繼續。然而,在餘額比金額更少的狀況下,而後序列進入跳轉組合碎片,它的消息被傳遞。一旦跳轉組合的消息的已經被傳遞,序列不發送任何 其它消息就退出(舉例來講,addDebitTransaction)。
注意有關跳轉的一件重要的事是,它們只引發一個封閉交互的序列退出,沒必要完成圖中描述的序列。在這種狀況下,跳轉組合是變體或者循環的一部分,而後只是變體或循環被退出。
並行
今天的現代計算機系統在複雜性和有時執行併發任務方面不斷進步。當完成一個複雜任務須要的處理時間比但願的長的時候,一些系統採用並行處理進程的各部分。當創造一個序列圖,顯示並行處理活動的時候,須要使用並行組合碎片元件。
並行組合碎片使用一個框架來畫,你把文本「par」放在框架的 namebox 中。而後你把框架的內容段用虛線分爲水平操做元。框架的每一個操做元表示一個在並行運行的線程。
圖 17 可能沒有舉例說明作並行活動的對象的最好的計算機系統實例,不過提供了一個容易理解的並行活動序列的例子。序列如這樣進行:hungryPerson 傳遞 cookFood 消息給oven 對象。當oven 對象接收那個消息時,它同時發送兩個消息(nukeFood 和 rotateFood)給它自己。這些消息都處理後,hungryPerson 對象從oven 對象返回 yummyFood 。
總結
序列圖是一個用來記錄系統需求,和整理系統設計的好圖。序列圖是如此好用的理由是,由於它按照交互發生的時間順序,顯示了系統中對象間的交互邏輯。
腳註
1 在徹底建模系統中,對象(類的實例)也將會在系統的類圖中建模。
2 當閱讀這個序列圖時,假定分析師登陸進入系統以內。
3 請注意,附着在不一樣的變體操做元上的、兩個或更多的約束條件式的確可能同時是真,可是實際最多隻有一個操做元將會在運行時發生(那種狀況下變體的「wins」沒有按照 UML 標準定義)。
4 雖然操做元看起來很是象公路上的小路,可是我特別不叫它們小路。泳道是在活動圖上使用的 UML 符號。請參考The Rational Edge 早期關於 活動圖的文章。
5 一般,附上約束的生命線是擁有包含在約束表達式中的變量的生命線。
6 關於選擇項組合碎片,循環組合碎片不須要在它上放置一個約束條件。
7 可能重用任何類型的序列圖(舉例來講,程序或業務)。我只是發現開發者更喜歡按功能分解他們的圖。