本章深入探討序列圖(Sequence Diagrams),它是 UML 中最常使用的動態模型。序列圖非常適合用來解釋物件之間的協作方式,但不應該被濫用——過多的序列圖與沒有序列圖一樣糟糕。

重點: 不要為系統中的每一個方法都畫序列圖。只在需要解釋一組物件如何協作來完成某個行為時才使用。大多數時候,程式碼本身比序列圖更具表達力。

基本語法#

Figure 18.1: Typical sequence diagram

序列圖的基本元素:

  • 生命線(Lifeline):從物件向下延伸的虛線
  • 訊息(Message):水平箭頭,對應方法呼叫
  • 啟動框(Activation):生命線上的窄矩形,表示方法正在執行中
  • 資料標記(Data Token):在訊息旁標註的參數或回傳值

物件的建立與銷毀#

Figure 18.2: Creating an object

Figure 18.3: Releasing an object to the garbage collector

  • 建立:訊息箭頭指向物件圖示本身(而非生命線)
  • 銷毀:在 C# 中由 Garbage Collector 處理,以 X 符號標示在生命線底部

簡單迴圈#

Figure 18.4: A simple loop

迴圈可以用矩形框搭配 *[condition] 標記來表示。

Cases 與 Scenarios#

序列圖應該保持小而聚焦。當一個場景變得過於複雜時,應該拆分成多張圖。

Figure 18.5: An overly complex sequence diagram

注意: 上圖就是一個反面教材——試圖在一張序列圖中塞入太多資訊。這種圖比程式碼更難理解,失去了使用 UML 的意義。

正確的做法是畫出高層次的概觀,只呈現物件之間的主要互動:

Figure 18.7: A high-level view

技巧: 好的序列圖應該能在三十秒內被理解。如果你的圖需要花五分鐘才能看懂,它就太複雜了。

進階概念#

迴圈與條件#

Figure 18.8: Sequence diagram with loops and conditions

UML 2.0 引入了 loopalt 框架來表示迴圈與條件判斷。然而作者建議避免在序列圖中使用過多的流程控制——這些邏輯用程式碼表達更加清楚。

需要時間的訊息#

當訊息的傳遞需要顯著的時間(例如網路通訊),可以用傾斜的箭頭來表示:

Figure 18.10: Failed phone call

傾斜箭頭的好處是能直觀地呈現競態條件(Race Condition)——當兩個訊息在傳輸途中交錯時,可能產生意想不到的行為。

非同步訊息(Asynchronous Messages)#

Figure 18.11: Asynchronous message

Figure 18.12: Older, better way to depict asynchronous messages

非同步訊息的表示方式:

  • UML 2.0:使用空心箭頭
  • 舊版 UML:使用半箭頭(作者認為這個符號更直覺)

非同步訊息代表送出者不等待回應就繼續執行,適合用在事件驅動或訊息佇列的場景。

多執行緒(Multiple Threads)#

Figure 18.13: Multiple threads of control

多執行緒可以透過在訊息標號前加上執行緒識別碼來表示,例如 T1:message

活躍物件(Active Objects)#

Figure 18.14: Active object

活躍物件以粗邊框表示,代表該物件持有自己的執行緒。

傳送訊息給介面#

Figure 18.15: Simple logger design

Figure 18.16: Sending to an interface

Figure 18.17: Sending to a derived type through an interface

當訊息的接收者是一個介面(Interface)而非具體類別時,可以用介面型別來命名物件。這在描述多型(Polymorphism)行為時非常有用。

本章小結#

序列圖是 UML 中最實用的動態模型,但必須遵循少即是多的原則:

  • 只在需要解釋物件協作時使用
  • 保持圖面簡潔,避免塞入過多流程控制邏輯
  • 程式碼能清楚表達的邏輯,不需要用序列圖重複表達

補充: 作者的經驗法則:寧可畫太少,也不要畫太多。一張精心選擇的序列圖勝過十張機械性產出的圖。