狀態(tài)圖(Statechart Diagram)是描述一個實(shí)體基于事件反應(yīng)的動態(tài)行為,顯示了該實(shí)體如何根據(jù)當(dāng)前所處的狀態(tài)對不同的時間做出反應(yīng)的。通常我們創(chuàng)建一個UML狀態(tài)圖是為了以下的研究目的:研究類、角色、子系統(tǒng)、或組件的復(fù)雜行為。
狀態(tài)圖用于顯示狀態(tài)機(jī)(它指定對象所在的狀態(tài)序列)、使對象達(dá)到這些狀態(tài)的事件和條件、以及達(dá)到這些狀態(tài)時所發(fā)生的操作。
狀態(tài)機(jī)用于對模型元素的動態(tài)行為進(jìn)行建模,更具體地說,就是對系統(tǒng)行為中受事件驅(qū)動的方面進(jìn)行建模(請參見概念:事件與信號)。狀態(tài)機(jī)專門用于定義依賴于狀態(tài)的行為(即根據(jù)模型元素所處的狀態(tài)而有所變化的行為)。其行為不會隨著其元素狀態(tài)發(fā)生變化的模型元素不需要用狀態(tài)機(jī)來描述其行為(這些元素通常是主要負(fù)載管理數(shù)據(jù)的被動類)。
狀態(tài)機(jī)由狀態(tài)組成,各狀態(tài)由轉(zhuǎn)移鏈接在一起。狀態(tài)是對象執(zhí)行某項(xiàng)活動或等待某個事件時的條件。轉(zhuǎn)移是兩個狀態(tài)之間的關(guān)系,它由某個事件觸發(fā),然后執(zhí)行特定的操作或評估并導(dǎo)致特定的結(jié)束狀態(tài)。圖 1 描繪了狀態(tài)機(jī)的各種元素。

一個簡單的編輯器可被視為有限的狀態(tài)機(jī),其狀態(tài)為 Empty(空)、Waiting for a command(等待命令)和 Waiting for text(等待文本)。事件 Load file(裝載文件)、Insert text(插入文本)、Insert character(插入字符)和 Save and quit(保存并退出)導(dǎo)致了狀態(tài)機(jī)中的轉(zhuǎn)移。下面的圖 2 描繪了編輯器的狀態(tài)機(jī)。

狀態(tài)
狀態(tài)是對象執(zhí)行某項(xiàng)活動或等待某個事件時的條件。對象可能會在有限的時間長度內(nèi)保持某一狀態(tài)。狀態(tài)具有以下幾項(xiàng)特征:

轉(zhuǎn)移
- 事件觸發(fā)器
- 警戒條件
- 操作
- 進(jìn)入和退出操作
- 內(nèi)部轉(zhuǎn)移
- 延遲的事件
轉(zhuǎn)移是兩個狀態(tài)之間的關(guān)系,它表示當(dāng)發(fā)生指定事件并且滿足指定條件時,第一個狀態(tài)中的對象將執(zhí)行某些操作并進(jìn)入第二個狀態(tài)。當(dāng)發(fā)生這種狀態(tài)變更時,即“觸發(fā)”了轉(zhuǎn)移。在觸發(fā)轉(zhuǎn)移之前,可認(rèn)為對象處于“源”狀態(tài);在觸發(fā)轉(zhuǎn)移之后,可認(rèn)為對象處于“目標(biāo)”狀態(tài)。轉(zhuǎn)移具有以下幾項(xiàng)特征:

一個轉(zhuǎn)移可能有多個源狀態(tài),在這種情況下,它將呈現(xiàn)為一個從多個并行狀態(tài)出發(fā)的結(jié)合點(diǎn);一個轉(zhuǎn)移也可能有多個目標(biāo)狀態(tài),在這種情況下,它將呈現(xiàn)為一個到多個并發(fā)狀態(tài)的叉形圖。
事件觸發(fā)器
在狀態(tài)機(jī)環(huán)境中,事件是指可觸發(fā)狀態(tài)轉(zhuǎn)移的激勵的發(fā)生。事件可能包括信號、調(diào)用、時間推移或狀態(tài)變更。信號或調(diào)用可能具有其值可用于轉(zhuǎn)移的參數(shù),其中包括警戒條件和操作的表達(dá)式。也可能會有無觸發(fā)器的轉(zhuǎn)移,這樣的轉(zhuǎn)移沒有事件觸發(fā)器。這種轉(zhuǎn)移也被稱為完成轉(zhuǎn)移,它們在源狀態(tài)完成其活動后將被隱含觸發(fā)。
警戒條件
當(dāng)轉(zhuǎn)移的觸發(fā)事件發(fā)生時,將對警戒條件進(jìn)行求值。只要警戒條件不重疊,就可能會有來自同一源狀態(tài)并具有同一事件觸發(fā)器的多個轉(zhuǎn)移。在事件發(fā)生時,只為轉(zhuǎn)移進(jìn)行一次警戒條件求值。該布爾表達(dá)式可能會引用對象的狀態(tài)。
操作
操作是可執(zhí)行的、不可分割的計(jì)算過程,這意味著,它不會被事件中斷,而會一直運(yùn)行到結(jié)束為止。它與活動正好相對,因?yàn)榛顒涌赡鼙黄渌录袛唷2僮骺梢园ú僮髡{(diào)用(調(diào)用狀態(tài)機(jī)的擁有者以及其他可見對象)、創(chuàng)建或破壞其他對象、或者向另一個對象發(fā)送信號。在發(fā)送信號的情況下,信號名稱以關(guān)鍵字“send”為前綴。
進(jìn)入和退出操作
每當(dāng)進(jìn)入或退出狀態(tài)時,進(jìn)入和退出操作將分別允許發(fā)出同一操作。這可以通過進(jìn)入和退出操作來順利地完成,而不必明確地將操作放在每個輸入或輸出轉(zhuǎn)移上。進(jìn)入和退出操作可能沒有實(shí)參或警戒條件。位于模型元素的狀態(tài)機(jī)頂層的進(jìn)入操作可能具有特定的參數(shù),這些參數(shù)代表了在創(chuàng)建該模型元素時狀態(tài)機(jī)所接收到的實(shí)參。
內(nèi)部轉(zhuǎn)移
內(nèi)部轉(zhuǎn)移使事件可以在不退出狀態(tài)的情況下在狀態(tài)內(nèi)得到處理,從而可避免觸發(fā)進(jìn)入或退出操作。內(nèi)部轉(zhuǎn)移可能會有帶參數(shù)和警戒條件的事件,它們所代表的基本上是中斷處理程序。
延遲的事件
延遲的事件是其處理過程被推遲的事件,它們的處理過程要到事件不被延遲的狀態(tài)被激活時才會執(zhí)行。當(dāng)該狀態(tài)被激活時,將觸發(fā)該事件,同時可能導(dǎo)致轉(zhuǎn)移(好象該事件剛剛發(fā)生)。要實(shí)施延遲的事件,需要有事件的內(nèi)部隊(duì)列。如果事件已發(fā)生但被列為延遲,它就會被添加到隊(duì)列中。當(dāng)對象進(jìn)入了不會使事件延遲的狀態(tài)時,將立即從該隊(duì)列中取出這些事件。
子狀態(tài)
簡單狀態(tài)是沒有子結(jié)構(gòu)的狀態(tài)。具有子狀態(tài)(嵌套狀態(tài))的狀態(tài)被稱為復(fù)合狀態(tài)。子狀態(tài)可能被嵌套到任意級別。嵌套的狀態(tài)機(jī)最多可能有一個初始狀態(tài)和一個終止?fàn)顟B(tài)。通過顯示某些狀態(tài)只能在特定環(huán)境(包含狀態(tài))中存在,子狀態(tài)可以簡化復(fù)雜的平面狀態(tài)機(jī)。

轉(zhuǎn)移的源狀態(tài)是包含復(fù)合狀態(tài)之外的源狀態(tài),其目標(biāo)狀態(tài)可能是復(fù)合狀態(tài)或子狀態(tài)。如果其目標(biāo)狀態(tài)是復(fù)合狀態(tài),嵌套的狀態(tài)機(jī)就必須包括一個初始狀態(tài),在進(jìn)入復(fù)合狀態(tài)之后并在發(fā)出它的進(jìn)入操作(如果有)之后,控制權(quán)將被傳遞給該初始狀態(tài)。如果其目標(biāo)狀態(tài)是嵌套狀態(tài),那么,在發(fā)出復(fù)合狀態(tài)的進(jìn)入操作(如果有)并發(fā)出嵌套狀態(tài)的進(jìn)入操作(如果有)后,控制權(quán)將被傳遞給該嵌套狀態(tài)。
從復(fù)合狀態(tài)出發(fā)的轉(zhuǎn)移可能會以復(fù)合狀態(tài)或子狀態(tài)作為它的源狀態(tài)。在這兩種情況下,控制權(quán)先離開嵌套狀態(tài)(并在可能的情況下發(fā)出它的退出操作),然后離開復(fù)合狀態(tài)(并在可能的情況下發(fā)出它的退出操作)。其源狀態(tài)為復(fù)合狀態(tài)的轉(zhuǎn)移基本上會中斷嵌套狀態(tài)機(jī)的活動。
歷史狀態(tài)
除非另有指定,當(dāng)轉(zhuǎn)移進(jìn)入復(fù)合狀態(tài)時,嵌套狀態(tài)機(jī)的操作將從初始狀態(tài)開始重新執(zhí)行(除非轉(zhuǎn)移直接以子狀態(tài)為目標(biāo))。歷史狀態(tài)使?fàn)顟B(tài)機(jī)可以重新進(jìn)入在它退出復(fù)合狀態(tài)之前的最后一個活動子狀態(tài)。圖 4 顯示了如何使用歷史狀態(tài)的示例。

常用的建模技術(shù)
狀態(tài)機(jī)最多地用于建立對象在其生命期內(nèi)的行為模型。當(dāng)對象具有依賴于狀態(tài)的行為時,尤其需要使用狀態(tài)機(jī)。可能具有狀態(tài)機(jī)的對象包括:類、子系統(tǒng)、用例、接口(以聲明實(shí)現(xiàn)該接口的對象必須滿足的狀態(tài))和協(xié)議(以聲明實(shí)現(xiàn)該協(xié)議的對象必須滿足的狀態(tài))。并非所有對象都需要有狀態(tài)機(jī)。如果對象的行為很簡單,只是存儲或檢索數(shù)據(jù),那么該對象的行為就與狀態(tài)無關(guān),它的狀態(tài)機(jī)也沒有多少用處。
要建立對象生命期的模型,需要包括三個事項(xiàng):指定對象可以響應(yīng)的事件、指定對這些事件作出的響應(yīng)以及指定過去行為對當(dāng)前行為的影響。對象生命期的建模還涉及到確定對象有意義地響應(yīng)事件的順序,即從創(chuàng)建對象時開始,繼續(xù)到該對象被破壞時為止。
要建立對象生命期的模型:
將狀態(tài)機(jī)的環(huán)境設(shè)置為類、用例或整個系統(tǒng)。
如果環(huán)境是類或用例,則要收集相鄰的類,其中包括父類或通過關(guān)聯(lián)關(guān)系或依賴關(guān)系可以接觸到的類。這些相鄰類是操作的候選目標(biāo),并且是可以包括在警戒條件中的候選目標(biāo)。
如果環(huán)境是整個系統(tǒng),則要將重點(diǎn)集中到系統(tǒng)的一個行為上,然后考慮在該方面涉及到的對象的生命期。整個系統(tǒng)的生命期通常會大得無法成為有意義的重點(diǎn)。
確定對象的初始狀態(tài)和終止?fàn)顟B(tài)。如果初始和終止?fàn)顟B(tài)具有前提條件和后續(xù)條件,也應(yīng)將這些條件定義出來。
確定對象要響應(yīng)的事件。這些事件可以在對象的接口或協(xié)議中找到。
按照從初始狀態(tài)到終止?fàn)顟B(tài)的順序,列出對象可能處于的頂層狀態(tài)。將這些狀態(tài)與相應(yīng)事件所觸發(fā)的轉(zhuǎn)移連接起來。然后添加這些轉(zhuǎn)移。
確定所有進(jìn)入操作或退出操作。
通過使用子狀態(tài)來擴(kuò)展或簡化狀態(tài)機(jī)。
檢查狀態(tài)機(jī)中的所有事件觸發(fā)轉(zhuǎn)移是否與該對象實(shí)現(xiàn)的接口或協(xié)議所期望的事件相符。同樣,檢查對象的接口或協(xié)議所期望的所有事件是否都得到了狀態(tài)機(jī)的處理。最后,確定要在哪些地方明確地忽略事件(如延遲的事件)。
檢查狀態(tài)機(jī)中的所有操作是否都得到了包含對象的關(guān)系、方法和操作的支持。
跟蹤狀態(tài)機(jī),將它與事件及其響應(yīng)的預(yù)期序列進(jìn)行比較。搜索無法達(dá)到的狀態(tài)以及狀態(tài)機(jī)無法繼續(xù)向前的狀態(tài)。
如果要重新布置或重新構(gòu)建狀態(tài)機(jī),需檢查并確保語義沒有發(fā)生變更。
提示與技巧
當(dāng)給定一項(xiàng)選擇時,要使用狀態(tài)機(jī)的可視語義,而不要寫出詳細(xì)的轉(zhuǎn)移代碼。例如,不要用幾個信號觸發(fā)一個轉(zhuǎn)移,然后使用詳細(xì)代碼來管理以不同的方式依賴于信號的控制流。應(yīng)使用由單獨(dú)的信號來觸發(fā)的單獨(dú)轉(zhuǎn)移。在隱藏了附加行為的轉(zhuǎn)移代碼中,要避免使用條件邏輯。
根據(jù)在狀態(tài)期間等待的事件或正在發(fā)生的事件來命名狀態(tài)。記住,狀態(tài)不是“時間點(diǎn)”;它是狀態(tài)機(jī)等待某個事件發(fā)生的時間段。例如,“waitingForEnd”這一名稱比“end”更好;“timingSomeActivity”比“timeout”更好。不要讓狀態(tài)的名稱看起來象是操作名。
在一個狀態(tài)機(jī)內(nèi)唯一地命名所有狀態(tài)和轉(zhuǎn)移;這將便于進(jìn)行源級別的調(diào)試。
謹(jǐn)慎使用狀態(tài)變量;不要在創(chuàng)建新狀態(tài)時使用它們。如果狀態(tài)不多,很少帶有或不帶有依賴于狀態(tài)的行為,并且很少有或根本沒有可能與包含狀態(tài)機(jī)的封裝體并行或獨(dú)立的行為,就可以使用狀態(tài)變量。如果有復(fù)雜的、依賴于狀態(tài)的潛在并行行為,或者如果必須處理的事件可能來自于包含狀態(tài)機(jī)的封裝體之外,則應(yīng)考慮使用構(gòu)件封裝體。
如果單個圖中的狀態(tài)超過 5 * 2 個,就應(yīng)考慮使用子狀態(tài)。在這里可以應(yīng)用我們的常識:在一個非常規(guī)則的模式中可以有十個狀態(tài),但如果兩個狀態(tài)之間具有四十個轉(zhuǎn)移,顯然就需要重新考慮了。務(wù)必要使?fàn)顟B(tài)機(jī)易于理解。
使用觸發(fā)事件的事件和/或在轉(zhuǎn)移期間發(fā)生的事件為轉(zhuǎn)移命名。選擇更加易于理解的名稱。
當(dāng)您看見一個選擇點(diǎn)時,應(yīng)考慮是否可以將作出該選擇的職責(zé)委托給另一個構(gòu)件,以便將其作為一組將不同的信號提供給封裝體遵照執(zhí)行(例如,代替對消息->數(shù)據(jù) > x 的選擇),并考慮是否可以讓發(fā)送方或另一中間主角來作出決定,然后通過在信號名稱中明確顯示該決定的方式發(fā)送信號(例如,使用名為 isFull 和 isEmpty 的信號,而不是以值命名信號并檢查消息數(shù)據(jù))。
為在選擇點(diǎn)中回答的問題指定描述性的名稱,例如“isThereStillLife”或“isItTimeToComplain”。
在任何給定的封裝體中,盡量使選擇點(diǎn)名稱保持唯一(其原因與轉(zhuǎn)移名稱需保持唯一相同)。
轉(zhuǎn)移的代碼段是否太長?是否應(yīng)使用函數(shù)來代替它們,是否將常用代碼段記錄為函數(shù)?轉(zhuǎn)移應(yīng)該類似于高層的偽代碼,并且應(yīng)當(dāng)遵循與 C++ 函數(shù)相同或更嚴(yán)格的長度規(guī)則。例如,代碼超過 25 行的轉(zhuǎn)移可被認(rèn)為是過長。
應(yīng)根據(jù)函數(shù)執(zhí)行的操作來命名函數(shù)。
要特別注意進(jìn)入和退出操作:在進(jìn)行更改后忘記更改相應(yīng)進(jìn)入和退出操作的情況尤其容易發(fā)生。
退出操作可用于提供安全性功能,例如,從“heaterOn”狀態(tài)中的退出操作將關(guān)閉加熱器,在這里,操作被用來強(qiáng)制執(zhí)行一個斷言語句。
通常,除非狀態(tài)機(jī)是抽象的并且將由包含元素的子類來進(jìn)行改進(jìn),否則子狀態(tài)應(yīng)包含兩個或更多個狀態(tài)。
應(yīng)該用選擇點(diǎn)來代替操作或轉(zhuǎn)移中的條件邏輯。選擇點(diǎn)容易被看到,而代碼中的條件邏輯則是不可見的,很容易被忽略。
避免使用警戒條件。
如果事件觸發(fā)了幾個轉(zhuǎn)移,將無法控制首先對哪個警戒條件求值。這會產(chǎn)生無法預(yù)料的結(jié)果。
可能有多個警戒條件為“True”,但隨后只能有一個轉(zhuǎn)移。所選擇的路徑是無法預(yù)料的。
警戒條件是不可見的;要“看見”它們的出現(xiàn)更是困難。
避免使用類似流程圖的狀態(tài)機(jī)。
這可能表示您試圖對并不實(shí)際存在的抽象概念進(jìn)行建模,例如:使用一個封裝體來對最適合于數(shù)據(jù)類的行為進(jìn)行建模,或通過使用緊密耦合的數(shù)據(jù)類和封裝體類來對數(shù)據(jù)類建模(例如,數(shù)據(jù)類用于向四周傳遞類型信息,但封裝體類包含了應(yīng)與數(shù)據(jù)類相關(guān)聯(lián)的大部分?jǐn)?shù)據(jù))。
狀態(tài)機(jī)的這種錯誤用法可以通過以下故障現(xiàn)象來識別:
被發(fā)送給“自己”的消息,主要是為了重復(fù)使用代碼
幾乎沒有狀態(tài),但有很多選擇點(diǎn)
在某些情況下沒有循環(huán)的狀態(tài)機(jī)。在流程控制應(yīng)用程序中,或者在試圖控制一個事件序列時,這樣的狀態(tài)機(jī)是有效的;如果它們在分析過程中出現(xiàn),則表示狀態(tài)機(jī)已退化為流程圖。
當(dāng)發(fā)現(xiàn)問題時,應(yīng)采取以下措施:
考慮將封裝體分解為職責(zé)更明確的小單元,將更多的行為轉(zhuǎn)移到與有問題的封裝體相關(guān)聯(lián)的數(shù)據(jù)類中。
將更多的行為轉(zhuǎn)移到封裝體類函數(shù)中。
制作更有意義的信號,以避免對數(shù)據(jù)的依賴。
使用抽象狀態(tài)機(jī)進(jìn)行設(shè)計(jì)
抽象狀態(tài)機(jī)是需要添加更多細(xì)節(jié)才能實(shí)際使用的狀態(tài)機(jī)。抽象狀態(tài)機(jī)可用于定義可復(fù)用的一般行為,這些行為將在隨后的模型元素中得到進(jìn)一步的改進(jìn)。

請考慮圖 5 中的抽象狀態(tài)機(jī)。例如,圖 5 中的簡單狀態(tài)機(jī)代表了實(shí)時系統(tǒng)中許多不同元素類型的最抽象的行為(自動“控制”)。盡管不同的元素類型都具有這一最高抽象程度,但是,根據(jù)其目的,它們可能在“Running”狀態(tài)中具有非常不同的詳細(xì)行為。因此,該狀態(tài)機(jī)最有可能在某個抽象封裝體類中定義,該封裝體類被用作不同的專用封裝體類的根類。
因此,我們將使用繼承來定義該抽象狀態(tài)機(jī)的兩種不同的改進(jìn)形式。圖 6 顯示了這兩種改進(jìn)形式 R1 和 R2。為清晰起見,我們使用淺灰筆來描繪從父類繼承而來的元素。

這兩種改進(jìn)形式的明顯差異在于它們分解“Running”(正在運(yùn)行)狀態(tài)的方式,以及它們擴(kuò)展原“start”(開始)轉(zhuǎn)移的方式。當(dāng)然,只有當(dāng)改進(jìn)形式已知,因而未在抽象類中使用單個端到端轉(zhuǎn)移來實(shí)施時,才能作出這些選擇。
鏈?zhǔn)綘顟B(tài)
對于上述的改進(jìn)類型而言,能夠同時“繼續(xù)”輸入轉(zhuǎn)移和輸出轉(zhuǎn)移是基本的能力。結(jié)合使用進(jìn)入點(diǎn)、終止?fàn)顟B(tài)和繼續(xù)轉(zhuǎn)移似乎就足以提供這些語義。但是,如果有多個不同的轉(zhuǎn)移需要擴(kuò)展,這就行不通了。
在這種情況下,抽象行為模式需要的是用一種方法把在單個運(yùn)行至結(jié)束的步驟中全部執(zhí)行的兩個或更多個轉(zhuǎn)移段鏈接起來。這意味著,要將進(jìn)入分層結(jié)構(gòu)狀態(tài)的轉(zhuǎn)移拆分成一個在狀態(tài)邊界處有效終止的進(jìn)入部分,以及一個在狀態(tài)內(nèi)繼續(xù)的擴(kuò)展部分。同樣,將分層嵌套的狀態(tài)所發(fā)出的輸出轉(zhuǎn)移分為一個在包含狀態(tài)邊界處終止的部分,以及一個從狀態(tài)邊界繼續(xù)到目標(biāo)狀態(tài)的部分。通過引入鏈?zhǔn)綘顟B(tài)的概念,可以在 UML 中獲得這一效果。它通過 UML 狀態(tài)概念的原型 (?chainState?) 來建模。該狀態(tài)的唯一目的是將更多的自動(無觸發(fā)器)轉(zhuǎn)移“鏈接”到輸入轉(zhuǎn)移上。鏈?zhǔn)綘顟B(tài)沒有內(nèi)部結(jié)構(gòu):沒有進(jìn)入操作,沒有內(nèi)部活動,沒有退出操作。它也沒有由事件觸發(fā)的轉(zhuǎn)移。但它可以有任意數(shù)量的輸入轉(zhuǎn)移。鏈?zhǔn)綘顟B(tài)可能有不帶觸發(fā)事件的輸出轉(zhuǎn)移;當(dāng)輸入轉(zhuǎn)移激活鏈?zhǔn)綘顟B(tài)時,該轉(zhuǎn)移將自動觸發(fā)。這種狀態(tài)的目的是將輸入轉(zhuǎn)移鏈接到獨(dú)立的輸出轉(zhuǎn)移上。在輸入轉(zhuǎn)移和被鏈接的輸出轉(zhuǎn)移之間,一個狀態(tài)連接到包含狀態(tài)內(nèi)部的另一個狀態(tài),而該狀態(tài)又連接到包含狀態(tài)外部的另一個狀態(tài)。引入鏈?zhǔn)綘顟B(tài)的目的是將包含狀態(tài)的內(nèi)部規(guī)約與其外部環(huán)境分隔開,這也是一種封裝。
實(shí)際上,鏈?zhǔn)綘顟B(tài)代表的是一種“串通”狀態(tài),它用于將某個轉(zhuǎn)移鏈接到一個特定的繼續(xù)轉(zhuǎn)移。如果沒有定義繼續(xù)轉(zhuǎn)移,轉(zhuǎn)移就會在鏈?zhǔn)綘顟B(tài)中終止。要使操作繼續(xù),就必須觸發(fā)包含狀態(tài)中的某一轉(zhuǎn)移。
圖 7 中的示例狀態(tài)機(jī)段顯示了鏈?zhǔn)綘顟B(tài)及其符號。鏈?zhǔn)綘顟B(tài)在狀態(tài)機(jī)圖中表示為在適當(dāng)分層結(jié)構(gòu)狀態(tài)內(nèi)的白色小圓(該符號類似于與它們相似的初始狀態(tài)和終止?fàn)顟B(tài))。圓是鏈?zhǔn)綘顟B(tài)原型的原型圖標(biāo),為了方便,通常把它們描繪在邊界附近。(實(shí)際上,另一種標(biāo)志法是把它們描繪在包含狀態(tài)的邊界上,類似于封裝體上的端口符號。)

該示例中,被鏈接的轉(zhuǎn)移包括三個被鏈接的轉(zhuǎn)移段 (e1/a11-/a12-/a13)。當(dāng)收到信號 e1 時,將調(diào)用標(biāo)記為 e1/a11 的轉(zhuǎn)移,執(zhí)行它的操作 a11,然后進(jìn)入鏈?zhǔn)綘顟B(tài) c1。接著,調(diào)用 c1 和 c2 之間的繼續(xù)轉(zhuǎn)移。最后,由于 c2 也是鏈?zhǔn)綘顟B(tài),所以從 c2 轉(zhuǎn)移到 S21。如果沿這些路徑的狀態(tài)都有退出和進(jìn)入操作,那么實(shí)際的操作執(zhí)行順序?qū)⑹牵?span lang="EN-US">
S11 的退出操作
操作 a11
S1 的退出操作
操作 a12
S2 的進(jìn)入操作
操作 a13
S21 的進(jìn)入操作
以上所有操作都將在單個運(yùn)行至結(jié)束的步驟中執(zhí)行。
應(yīng)將這些操作與直接轉(zhuǎn)移 e2/a2 的操作執(zhí)行語義進(jìn)行對比,后者是:
S11 的退出操作
S1 的退出操作
操作 a2
狀態(tài) S2 的進(jìn)入操作
狀態(tài) S21 的進(jìn)入操作 ? 1987 - 2001 Rational Software Corporation。版權(quán)所有。
通用準(zhǔn)則
當(dāng)行為的改變和狀態(tài)有關(guān)時才創(chuàng)建狀態(tài)圖。
敏捷建模( AM) ( Ambler 2002)的原則--最大化項(xiàng)目干系人的投資--建議你只有當(dāng)模型能夠提供正面價值的時候才創(chuàng)建模型。如果一個實(shí)體,比如一個類或組件,表示的行為的順序和當(dāng)前的狀態(tài)無關(guān),那么畫一個UML狀態(tài)圖可能是沒有什么用處的。例如一個SurfaceAddrESs類就很簡單,表示了那些你將會在系統(tǒng)中顯示和操作的數(shù)據(jù),因此一個UML狀態(tài)圖就沒有任何相關(guān)之處。而一個Seminar對象就非常的復(fù)雜,學(xué)生注冊這樣一個事件將會根據(jù)它的當(dāng)前狀態(tài)有不同的反應(yīng),就像你在圖1中看到的。

把初始狀態(tài)放置在左上角。
如你在圖1所見的,初始狀態(tài)被建模成一個實(shí)心圈,把初始狀態(tài)放在左上角反映西方人的閱讀文化的習(xí)慣。
把最終狀態(tài)放置在右下角。
如你在圖1所見,最終狀態(tài)被建模為一個帶邊界的實(shí)心圓。把最終狀態(tài)放右下角反映了西方的文化的從左到右,從上到下的閱讀習(xí)慣。
狀態(tài)指南
狀態(tài)是一個實(shí)體的行為模式的某個階段。 狀態(tài)的表示是通過實(shí)體的屬性值。 例如,在圖1中,當(dāng)seminar被標(biāo)記為open,并且存在空位的時候,seminar就處于Open For Enrollment的狀態(tài)。
狀態(tài)名稱要簡單但應(yīng)具有描述性。
象Open For Enrollment和PropOSed這種的狀態(tài)名稱很容易理解,從而提高了圖⒈的溝通價值。理論上狀態(tài)名稱應(yīng)該是現(xiàn)在時,但是用過去式寫成的諸如Proposed的名稱要比用現(xiàn)在時寫成的諸如Is Proposed的名稱好的多。
避免"黑洞"狀態(tài)。
黑洞狀態(tài)是那種只有變換進(jìn)來但沒有任何變換發(fā)出的狀態(tài),這種情況要么由于該狀態(tài)是一個最終狀態(tài),要么就是你已經(jīng)錯過了一個或多個變換變換。
避免"奇跡"狀態(tài)。
奇跡狀態(tài)是那種只有變換發(fā)出但沒有任何變換進(jìn)來的狀態(tài),這種情況要么由于該狀態(tài)是一個起點(diǎn),要么就是你已經(jīng)錯過了一個或多個變換變換。
子狀態(tài)建模指南
為復(fù)雜的目標(biāo)建模子狀態(tài)。
圖1中展示的UML狀態(tài)圖是不完整的,因?yàn)樗鼪]有建模Seminar的poST - enrollment(注冊后)狀態(tài)。 圖2建模了一個Seminar的完整的生命周期,把圖1描述為一個新的包括子狀態(tài)集合的Enrollment的復(fù)合狀態(tài),也稱作超狀態(tài)。注意按理說你會像圖1的模型那樣處理標(biāo)記,但為了簡化起見在原先變換上的標(biāo)記都沒有包括在內(nèi)。當(dāng)一個現(xiàn)有狀態(tài)表現(xiàn)出復(fù)雜的行為時,建模子狀態(tài)就是有意義的,從而促使你來研究它的子狀態(tài)。當(dāng)幾個現(xiàn)有狀態(tài)共用一個通用的入口條件或出口條件( DouglASs 1999)時,引入超狀態(tài)是有意義的,在圖1中你可以看到所有的狀態(tài)共用一個通用的closed變換,以到達(dá)最終狀態(tài)。

把通用的子狀態(tài)變換放在一起
和圖1中每一個子狀態(tài)都擁有一個cancelled變換不同,在圖2中你可以看到cancelled變換僅用于描述Enrollment超狀態(tài),這使圖形得到簡化。如果子狀態(tài)都共享一個入口變換或出口變換,都可以使用一個同樣的方法。 變換上的警戒點(diǎn)和動作(如果有)也應(yīng)該使相等的。
為復(fù)雜的實(shí)體創(chuàng)建一個分層的狀態(tài)圖
雖然這種表現(xiàn)子狀態(tài)的方法是很好使的,但是最終的圖可能變得相當(dāng)復(fù)雜--我們只要設(shè)想一下如果Being Taught狀態(tài)也有子狀態(tài)的話,圖2會變成什么樣就知道了。 一個替代的方法是創(chuàng)建一個分層的UML狀態(tài)圖。 例如,圖3表示高階視圖,而圖1描述了一個細(xì)節(jié)視圖。這種方法的好處是如果需要的話,馬上就可以建立一張?jiān)攬D來研究Being Taught狀態(tài)。

最高階的狀態(tài)圖總有初始態(tài)和最終態(tài)
一個高階的UML狀態(tài)圖,例如圖2描述的這樣,應(yīng)該表示實(shí)體的完整的生命周期,包括"出生"和最后的"死亡"。 低階的圖未必包含初始狀態(tài)和最終狀態(tài),特別是那些建模一個實(shí)體的生命周期的"中間狀態(tài)"的圖。
變換和動作
變換是從一種狀態(tài)到另一種狀態(tài)的序列,它可能是通過一個事件觸發(fā)的。簡而言之就是被建模的實(shí)體的內(nèi)部或外部的行為。 對一個類來說,變換一般是將會導(dǎo)致狀態(tài)的重要改變的操作調(diào)用的結(jié)果,因此我們需要了解一點(diǎn),并不是所有的方法調(diào)用都會導(dǎo)致變換產(chǎn)生的,這一點(diǎn)非常重要。一個動作就是某個東西,對類來說就是一個操作,被建模的實(shí)體所調(diào)用的操作。
用實(shí)現(xiàn)語言的命名規(guī)則命名軟件動作
圖1中的動作遵循Java操作的命名規(guī)則( Vermeulen et. 2000),因?yàn)橄到y(tǒng)使用
用敘述性文字命名角色動作
UML狀態(tài)圖可用于建模非軟件實(shí)體的生命周期,特別是UML圖上的角色。 例如學(xué)生角色就可能有諸如Accepted、Full Time、Part Time、Graduated、Masters、Doctoral、和Post - Doctoral等狀態(tài),以顯示各人的不同行為。 當(dāng)你在建模現(xiàn)實(shí)世界的角色時,與軟件中Student類不同的是,狀態(tài)間的變換最好是使用敘述性文字來描述,例如drop seminar和pay fees,而不是dropSeminar ()和payFees (),因?yàn)楝F(xiàn)實(shí)生活中的人是做事情,而不是執(zhí)行操作。
只有對所有的入口變換都合適時才注明入口動作
在圖1中你可以看到Closed To Enrollment狀態(tài)的入口中操作notifyInstructor ()都是經(jīng)由entry/動作標(biāo)記來調(diào)用的。 這暗示著每次進(jìn)入狀態(tài)時都需要調(diào)用該操作,如果你不希望每次都發(fā)生,那么就把動作關(guān)聯(lián)到特定的入口變換。例如,addStudent ()動作是在student enrolled變換到Open For Enrollment變換發(fā)生,而在到opened變換則不會發(fā)生,這是因?yàn)槊看文阍谶M(jìn)入該狀態(tài)并不需要增加一個學(xué)生。
只有對所有的出口變換適合時才注明出口動作
出口動作,用exit/標(biāo)記來表示,工作方式類似于入口動作。
只有當(dāng)你想終止并再進(jìn)入該狀態(tài)時才建模遞歸變換
一個遞歸的變換是那些兩個端點(diǎn)都擁有相同狀態(tài)的變換。 一個重要的暗示是實(shí)體從狀態(tài)出來,又回到原有的狀態(tài),因此,那些由于entry/或exit/動作標(biāo)記而被調(diào)用的任何一種操作都可能被自動調(diào)用。 圖1的Open For Enrollment狀態(tài)就是這種遞歸變換的例子,因此當(dāng)前班級大小就在入口處被記錄下來。
用過去式命名轉(zhuǎn)換事件
圖1中的轉(zhuǎn)換事件,例如seminar split和cancelled,是使用過去式命名的,反映了這樣一個事實(shí):變換是事件的結(jié)果--因?yàn)槭录l(fā)生在變換之前,因此應(yīng)該用過去式命名。
把轉(zhuǎn)換標(biāo)記放在接近源狀態(tài)的地方
雖然圖1比較復(fù)雜,變換標(biāo)記盡可能放在靠近來源的地方,例如seminar split和student enrolled。 Furthermore, the labels were justified (left and right respECtively) to help visually place them close to the source state.
以轉(zhuǎn)換方向?yàn)榛A(chǔ)放置變換標(biāo)記
為了更易于判斷哪個標(biāo)記和變換是一起的,按照如下的規(guī)則來放置變換標(biāo)記:
在變換線條上的從左到右。
在變換線條下的從右到左。
變換線條右邊的往下。
變換線條左邊的往上。
警戒點(diǎn)
一個警戒點(diǎn)是為了穿過一個轉(zhuǎn)換而必須為真的一個條件。
警戒點(diǎn)不應(yīng)該重疊
離開狀態(tài)的相似變換上的警戒點(diǎn)必須彼此一致。 舉例來說,x <0, x = 0,以及x > 0的警戒點(diǎn)是一致的,而x < = 0和x > = 0的警戒點(diǎn)就不是一致的,因?yàn)樗麄冎丿B了,它并沒有明確的指出當(dāng)x為0時將發(fā)生什么。在圖1中,你可以看到警界點(diǎn)的一致性,從填寫注冊表活動出發(fā)的該學(xué)生劃線變換上的警戒點(diǎn)沒有重疊,決策點(diǎn)上的警戒點(diǎn)也一樣。
為可視化的定位警戒點(diǎn)而引入接合點(diǎn)。
在圖2中你可以看到從Being Taught觸發(fā)student dropped事件存在兩個變換,而圖3中僅有一個,變換被合并了,因此我們需要一個接合點(diǎn)(填滿的圓)。 這種方法的好處是現(xiàn)在圖上的兩個警戒點(diǎn)更彼此接近了,更容易看出警戒點(diǎn)是否重疊。
警戒點(diǎn)不必配套
一個狀態(tài)的變換警戒點(diǎn)有可能是不完整的。例如,一個bank account對象可能從Open狀態(tài)變換到Needs Authorization狀態(tài),這時需要一個大額存款"large deposit"的警戒點(diǎn)。可是,一個帶有"small deposit"的警戒點(diǎn)的deposit變換可能并不需要建模,它是被隱含的,我們遵循了AM的實(shí)踐--簡單的描述模型和僅僅包括相關(guān)的信息。
一致的命名警戒點(diǎn)
圖1包含了諸如seat avAIlable和no seat available的警戒點(diǎn),兩個警戒點(diǎn)的描述是一致的。 然而,諸如seats left、no seat left、no seats left、no seats available、seat unavailable之類的描述就是不一致,而且難于理解的。