- 你所在位置:首页 〉VS.net〉UML〉基础知识〉状态图概述(2)
- 状态图概述(2)
- 作者:佚名 文章来源:http://squall.cs.ntou.edu.tw/ 发布日期:2008-02-28 浏览次数:475
-
- 打印这篇文章
-
什麼是物件的狀態?
大部分的物件都有內部狀態,物件對於訊息的反應都是根據其狀態來決定的,例如:
- 我會接電話,可是當電話鈴聲響起時我去不去接電話則必須看我當時忙不忙而定
- 電視機通常有 power/channel(+-)/volume(+-)/setup 幾個按鍵,但是 channel(+-)/volume(+-)/setup 按鍵通常需要在 power on 的狀況下才有作用, channel(+-)/volume(+-) 按鍵一般情況下是調整頻道和音量的,但是在 setup 按鍵被按下後 (進入設定模式),通常又都有另外的功能。
- 利用 iostream 函式庫做程式輸入輸出的動作時,透過一個 ifstream 的物件可以讀取檔案內的資料流,可是這個物件必須要代表一個已經開啟而且還沒有讀到 EOF 的檔案才可以
- stack 物件可以讓我們 push 資料進去,可是 stack 滿了的話就沒辦法 push 資料進去了,可以讓我們 pop 資料出來,可是 stack 空了的話就沒辦法 pop 資料出來了
注意:
- 只有功能很單純的物件是沒有狀態的,這種物件不管在任何時候接到訊息的反應都是一模一樣的,有內部狀態的物件其接收訊息的時序是很重要的
- 由物件的外部是沒辦法很容易地看出物件的狀態來的,除非透過物件定義的特殊界面,或是長時間地觀察物件的表現。

物件界面的使用方法是其內部狀態的外在表現:
公用成員函式與其預設的呼叫順序都是物件界面 (interface) 的一部份,其中 "函式呼叫的順序" 就代表了物件內部狀態的存在。使用一個物件時必須透過這一組界面、遵循其使用規則才可以正確地得到此物件所提供的功能。實作一個物件的時候,必須保證 "不管其它物件如何使用此組界面,物件都需要正確、合理地運作,不能夠隨便就當掉而有不合理的反應"
例如:
class MyClass { public: void open(); void connect(); void read(); void write(); void disconnect(); void close(); private: ... }; 使用規則:物件必須在沒有開啟的狀況下呼叫 open() 才有作用, 必須要已經 open() 過後才能夠呼叫 connect(),必須 要是 connected 的狀況下才能做 read/write/disconnect, 必須要是 disconnected 且有 open 過才能呼叫 close()正確用法: 錯誤用法: MyClass obj; obj.open(); obj.connect(); obj.read(); obj.disconnect(); obj.close();
MyClass obj; obj.open(); obj.read();
- 我會接電話,可是當電話鈴聲響起時我去不去接電話則必須看我當時忙不忙而定
![]()
直覺地實作物件的狀態:
在需要記錄狀態的地方以布林變數來記錄
void open()
{
if (!m_fOpen)
{
m_fOpen = true;
do_open();
}
}
|
void close()
{
if ((m_fOpen)&&(!m_fConnected))
{
m_fOpen = false;
do_close();
}
}
|
|
void connect()
{
if ((m_fOpen)&&(!m_fConnected))
{
m_fConnected = true;
do_connect();
}
}
|
void disconnect()
{
if (m_fConnected)
{
m_fConnected = false;
do_disconnect();
}
}
|
|
void read()
{
if (m_fConnected)
do_read();
}
|
void write()
{
if (m_fConnected)
do_write();
}
|
上面程式的考慮周全了嗎? 兩個 flag 共有 4 種狀況,可是每一個訊息處理函式中似乎都只考慮到部份的狀況,其它的狀況呢?
![]()
以狀態圖來全盤描述物件的動態反應:
狀態圖最大的好處在於 "完整地描述物件所有可能發生的狀況",注意下圖中必須畫出所有的狀態,同時每一個狀態下 outgoing branch 需要包含所有可能出現的事件 (訊息)
首先 m_fOpen 及 m_fConnected 兩個 flag 可以定義出 4 種狀況,其中有意義的有三種如下表,表中並指定每一種狀況一個狀態的名稱:
| m_fOpen | m_fConnected | 對應狀態 | |
| false | false | Closed | |
| true | false | Opened | |
| true | true | Connected |
第四種狀態 m_fOpen==false, m_fConnected==true 是不可能發生的狀態,不予考慮。
在下面的狀態圖中我們考慮三種狀態以及六種輸入訊息 (事件),這三種狀態是 Closed, Opened, 及 Connected,六種訊息是 open, connect, read, write, disconnect, close
![]()
實作狀態圖表示的物件:
用單一的狀態變數來實作狀態圖中物件的所有狀態
製作訊息 open 處理函式 open() 的方法如下:
- 在狀態圖中找到所有的 open 訊息 (我們知道 OO 中物件與物件的溝通是藉由事件傳遞來完成的),針對每一個不同的狀態來製作其回應動作,只有標註為 NOP 的狀態才能略過不予實作
void open()
{
if (m_state == Closed)
{
do_open();
m_state = Opened;
}
}
|
void close()
{
if (m_state == Opened)
{
do_close();
m_state = Closed;
}
}
|
|
void connect()
{
if (m_state == Opened)
{
do_connect();
m_state = Connected;
}
}
|
void disconnect()
{
if (m_state == Connected)
{
do_disconnect();
m_state = Opened;
}
}
|
|
void read()
{
if (m_state == Connected)
do_read();
}
|
void write()
{
if (m_state == Connected)
do_write();
}
|
注意:
- 假設更改一點點系統的要求:此物件允許在連線的狀況下直接處理 close 訊息的話,狀態圖修改如下圖左所示, close() 處理函式可以改寫為下圖右:
|
void close()
{
if (m_state == Opened)
{
do_close();
m_state = Closed;
}
else if (m_state == Connected)
{
do_disconnect();
do_close();
m_state = Closed;
}
}
|
![]()
結論:
在製作每一個類別的時候,如果能夠清楚的分析條列此類別物件的
- 所有能夠接受的訊息,
- 物件所有的狀態,
- 物件在不同狀態下對各種訊息的反應
在任何一種狀態下應該要考慮所有可能收到的訊息,才不會在某些沒有考慮到的操作狀況下產生不合適的反應。
類別關係圖及物件關係圖是設計程式者與使用此應用程式的人心中共同的靜態應用模型 (conceptual model),必需反映實際世界中真實的系統運作模型,每一個不同的物件的狀態圖則是該物件的完整動態模型,這兩種圖可以說完整地刻劃了整個物件系統,一般同一個程式計劃中不同的設計人員在討論時並不直接交換程式碼,而是共同討論這些圖片,這些圖片也可以和提出系統需求的使用者來溝通,同時也是系統標準文件中最重要的一部份。
基本上每一個類別是一個模組,和外界其它的物件之間只透過定義好的界面來溝通,在製作這個類別中的事件處理函式時不應該假設其它物件會以某種特定的方法來使用此物件,這也就是為什麼在每一個狀態下我們都需要考慮所有可能發生的訊息的原因,在很多情況下,物件不會去處理一些不該出現的訊息,也就是狀態圖中標示 NOP 的意義,但是這些不該出現的訊息,常常也設計成導致一些警告的訊息來提醒其他的物件他們的使用方式有誤,例如上例中:
這樣子設計出來的物件的獨立性很高,可以提供給任意其它的物件來組合系統,各種可能出現的狀況都已經考慮到了,不會有什麼意外的狀況發生。
