我在介紹性專欄中曾經解釋過,序列圖用於描述系統隨時間而產生的內部行為。因為系統行為是對象相互之間發送消息的結果,因此序列圖繪制了那些消息在對象之間移動時的路線。歸根結底,序列圖就是交互圖。在前一部分中,盡管我們描述了無數交互,但只創建了一個相當簡單的圖。這次,我們將做進一步的研究,看看 UML 指定的序列圖的兩種形態。這兩種形態是 常規和 實例。讓我們從每種形態的正確應用開始。
序列圖的兩種類型
序列圖用於描述對象之間兩種不同類型的交互。一種交互類型是 必須 (must) 交互,其中對象 A 必須向對象 B 發送特定消息。另一種交互類型是 可能 (may) 交互,其中對象 A 可能(但不一定)向對象 B 發送特定消息。這兩種形態的序列圖描述了這兩種不同類型的交互。常規形態描述的是 必須交互,而實例形態則描述了 可能交互。
常規形態的序列圖描述初始刺激因素所產生的類交互。常規形態則記述了初始刺激因素能夠產生的一切交互。成功和失敗條件與循環、條件和分支一樣,都是這種圖的組成部分。
常規序列圖在水平軸方向上的每個框中只包含一個類名,如圖 1 所示。它的含義是,交互背後的對象是匿名的,該類的任何對象都可以參與到交互中。因此,必須為所有路徑明確建模。在常規序列圖中,對象 A 必須向對象 B 發送模型中的一條消息。
圖 1. 常規序列圖
序列圖的第二種形態是實例形態。實例序列圖描述了兩個實例之間可能發生的單一消息交換。這樣的圖將在水平軸方向的框中包含一個變量名及其類類型,如圖 2 所示。這種形態不包括常規形態中常見的循環、條件和分支。在系統中實際的控制流程中,在交互過程中所進行的某些斷言可能為假。如果發現斷言為假,實例序列圖中的所有消息都為空,這種情形將不出現。實例序列圖描述了可能發生也可能不發生的單一情形。
圖 2. 實例序列圖
實例序列圖最適合於在軟件開發生命周期的分析階段對個別方案建模。常規序列圖可以為包含多個方案的整個用例建模。其它一些類型的活動 -- 例如為子系統或框架與其各個部分之間使用的協議建模 -- 可以使用任何一種形態,這取決於組件在軟件開發生命周期中所處的位置。與實例形態相比,常規形態更接近於在最終產品中出現的實際代碼。
我們在前一專欄中使用的是常規形態,並將在此繼續研究這種形態。這一次,我們將探究條件邏輯在常規序列圖中所扮演的角色,通過它來讓您了解有關 UML 表示的更多知識。
序列圖繪制中的條件邏輯
常規序列圖利用了條件邏輯,這對於描述交互過程中事件的可選流程來說很有用處。根據軟件開發生命周期中所處的不同階段,可以繪制詳略度不同的圖。在分析階段,您可能願意將詳細信息排除在條件表達式以外,而在設計階段,您卻可能希望將最終產品中要使用的代碼的片段包括在條件表達式中。
無論處於開發周期中的哪個階段,隨著條件表達式圖的繪制,序列圖與如 Java 語言這樣的面向對象語言之間那種自然的一致性就愈發清楚了。例如,請考慮一個允許出納員接受存款的銀行業務應用。除了其它一些事項以外,還規定了系統必須防止出納員把負的金額記入帳戶貸方,因為這會導致從帳戶中扣除。因此系統必須有一種檢查鍵入的所有金額均為正數的機制。清單 1 顯示了確保存款為正數的條件表達式。
清單 1. 帶有條件表達式的方法
\** This is a method in a Teller class **\
public void receiveDeposit(Account account, BigDecimal deposit)
throws ImproperDepositException {
// Check to ensure the deposit is positive
if (deposit.compareTo(new
BigDecimal(0.0)) > 0) {
account.credit(deposit);
}
else {
throw new ImproperDepositException();
}
}
在分析階段,您不是很關心細節,因此圖只需要表明存款為正數。在常規序列圖中,條件作為帶有消息名的保護機制出現,位於水平調用箭頭上方。這些保護條件用方括號括起,放在消息名的左側,如圖 3 所示。
圖 3. 在分析期間添加的條件
上述方法和序列圖之間的關系在圖 4 中顯現得更為清楚,我們在圖 4 中看到了在設計階段可能用到的更明確的圖。當然沒有顯示全部方法:缺少了 else 子句。不過,圖中消息箭頭的語義規定只能在條件有效時發送消息。
圖 4. 更明確的條件
可以通過在 Teller 類和 ImproperDepositException 之間添加另一個調用箭頭來為 else 子句建模。在這個調用上會有一個與 if 相反的條件;在本例中,即存款必須小於等於 0。您不妨自己嘗試為這個語句建模。
繪制 for 循環圖
如上例所示,常規序列圖 -- 以及實際上所有 UML 圖 -- 幾乎映射了 Java 語言的語法。所以,大多數 Java 開發者對這些圖都有一個直觀的理解,並且可以很快地學會如何使用它們。為進一步探討常規序列圖和 Java 語言之間的一致性,我們將繪制 for 循環圖,如清單 2 所示。
清單 2. for 循環
for ( int i =0; i < 4; i++) {
squareRoom.examineCorner(i);
}
在序列圖中,迭代是通過水平箭頭上消息名之前的星號 (*) 來表示的。如果迭代的次數已知並且固定 -- 這種情況非常少見 -- 這個數字出現在星號後面的方括號中。因為大多數 for 循環處理的復雜邏輯不允許靜態地確定迭代次數,因此您不會經常使用這種格式的括號表示。圖 5 顯示了上述 for 循環的序列圖。
圖 5. for 循環序列圖
繪制 while 循環圖
因為 while 循環將循環與條件結合起來,因此它是個非常容易接受的示例。我們將對清單 3 中顯示的 while 循環繪制圖。
清單 3. while 循環
while ( value.notFound() ) {
value = database.search( key );
}
我們的 while 循環圖既包含條件,又包含表明迭代的星號,但您會發現,沒有迭代的次數。 while 循環很少包含迭代次數 -- 除非它是一個偽裝的 for 循環。圖 6 顯示了 while 循環圖。
圖 6. while 循環序列圖
結束語
一般來說, 必須和 可能行為是 UML 和軟件開發的基本概念。用例捕捉 必須行為;方案捕捉 可能行為。類圖捕捉 必須行為;實例圖捕捉 可能行為。我主要討論這一概念是因為我發現許多人沒能掌握序列圖的根本靈活性,而分化成直覺和形態使用這兩個極端。
在閱讀這些文章時,您應該把精力集中在發展模型語義的直觀理解上。隨著看到越來越多的序列圖並開始創建自己的序列圖,您會發現許多序列圖依賴於條件邏輯和圖表上下文來說明圖所表示的是 必須還是 可能的系統視圖。隨著我們深入到更加復雜的圖表繪制技術,及早學習如何識別和使用這種差別將對您今後有所幫助。
除了探討序列圖繪制中 必須和 可能行為的重要性以外,我還向您介紹了如何在圖中表示條件和迭代。既然您已經知道如何繪制 for 和 while 循環圖,我建議您在其它 Java 構造(例如 do-while 循環)上實踐一下建模表示。隨著您自己練習繪制這些簡單構造圖,自然會逐漸加深對序列圖繪制的理解。