1 意圖
允許一個其對象在其內部狀態(tài)改變時改變它的行為欧漱,對象看起來似乎修改了它的類。
2 別名
狀態(tài)對象(Objects For States)
3 動機
考慮一個表示網絡連接的類TCPConnection缀程。一個TCPConnection對象的狀態(tài)處于若干不同狀態(tài)之一:連接已建立(Established)、正在監(jiān)聽(Listening)、連接已關閉(Closed)形庭。當一個TCPConnection對象收到其他對象的請求時劫侧,它根據自身的當前狀態(tài)作出不同的反應埋酬。例如,一個Open請求的結果依賴于該連接是處于連接已關閉狀態(tài)還是連接已建立狀態(tài)烧栋。State模式描述了TCPConnection如何在每一種狀態(tài)下表現(xiàn)出不同的行為写妥。
這一模式的關鍵思想是引入了一個稱為TCPState的抽象類來表示網絡的連接狀態(tài)。TCPState類為各表示不同的操作狀態(tài)的子類聲明了一個公共接口审姓。TCPState的子類實現(xiàn)與特定狀態(tài)相關的行為珍特。例如,TCPEstablished和TCPClosed類分別實現(xiàn)了特定于TCPConnection的連接已建立狀態(tài)和連接已關閉狀態(tài)的行為邑跪。
TCPConnection類維護一個表示TCP連接當前狀態(tài)的狀態(tài)對象(一個TCPState子類的實例)次坡。TCPConnection類將所有與狀態(tài)相關的請求委托給這個狀態(tài)對象。TCPConnection使用它的TCPState子類實例來執(zhí)行特定于連接狀態(tài)的操作画畅。
一旦連接狀態(tài)改變砸琅,TCPConnection對象就會改變它所使用的狀態(tài)對象。例如當連接從已建立狀態(tài)轉為已關閉狀態(tài)時轴踱,TCPConnection會用一個TCPClosed的實例來代替原來的TCPEstablished的實例症脂。
4 適用性
在下面的兩種情況下均可使用State模式:
- 一個對象的行為取決于它的狀態(tài),并且它必須在運行時刻根據狀態(tài)改變它的行為淫僻;
- 一個操作中含有龐大的多分支的條件語句诱篷,且這些分支依賴于該對象的狀態(tài)。這個狀態(tài)通常用一個或多個枚舉常量表示雳灵。通常 , 有多個操作包含這一相同的條件結構棕所。state模式將每一個條件分支放入一個獨立的類中。這使得你可以根據對象自身的情況將對象的狀態(tài)作為一個對象悯辙,這一對象可以不依賴于其他對象而獨立變化琳省。
5 結構
6 參與者
- Context(環(huán)境,如TCPConnection)
——定義客戶感興趣的接口
——維護一個ConcreteState子類的實例躲撰,這個實例定義當前的狀態(tài) - State(狀態(tài)针贬,如TCPState)
——定義一個接口以封裝與Context的一個特定狀態(tài)相關的行為 - ConcreteState subclasses(具體狀態(tài)子類,如TCPEstablished拢蛋,TCPListen桦他,TCPClosed)
——每一子類實現(xiàn)一個與Context的一個狀態(tài)相關的行為
7 協(xié)作
- Context將與狀態(tài)相關的請求委托給當前的ConcreteState對象處理;
- Context可將自身作為一個參數傳遞給處理該請求的狀態(tài)對象谆棱。這使得狀態(tài)對象在必要時可訪問Context快压;
- Context是客戶使用的主要接口圆仔。客戶可用狀態(tài)對象來配置一個Context嗓节,一旦一個Context配置完畢荧缘, 它的客戶不再需要直接與狀態(tài)對象打交道。
- Context或ConcreteState子類都可決定哪個狀態(tài)是另外哪一個的后繼者拦宣,以及是在何種條件下進行條件轉換截粗。
8 效果
State模式有下面一些效果:
- 1 它將與特定狀態(tài)相關的行為局部化,并且將不同狀態(tài)的行為分割開來
- 2 它使得狀態(tài)轉換顯式化:當一個對象僅以內部數據值來定義當前狀態(tài)時 , 其狀態(tài)僅表現(xiàn)為對一些變量的賦值鸵隧,這不夠明確绸罗。為不同的狀態(tài)引入獨立的對象使得轉換變得更加明確。而且, State對象可保證Context不會發(fā)生內部狀態(tài)不一致的情況豆瘫,因為從Context的角度看珊蟀,狀態(tài)轉換是原子的 — 只需重新綁定一個變量,(即Context的State對象變量)外驱,而無需為多個變量賦值育灸。
- 3 State對象可被共享:如果State對象沒有實例變量 — 即它們表示的狀態(tài)完全以它們的類型來編碼 — 那么各Context對象可以共享一個State對象。當狀態(tài)以這種方式被共享時 , 它們必然是沒有內部狀態(tài), 只有行為的輕量級對象昵宇。
9 實現(xiàn)
實現(xiàn)State模式有多方面的考慮:
- 1 誰定義狀態(tài)轉換:State模式不指定哪一個參與者定義狀態(tài)轉換準則磅崭。如果該準則是固定的, 那么它們可在Context中完全實現(xiàn)。然而若讓State子類自身指定它們的后繼狀態(tài)以及何時進行轉換 , 通常更靈活更合適瓦哎。這需要Context增加一個接口 , 讓State對象顯式地設定Context的當前狀態(tài)砸喻。
用這種方法分散轉換邏輯可以很容易地定義新的State子類來修改和擴展該邏輯。 這樣做的一個缺點是蒋譬,一個State子類至少擁有一個其他子類的信息 , 這就再各子類之間產生了實現(xiàn)依賴割岛。 - 2 基于表的另一種方法:在C++ Programming Style中,Cargil描述了另一種將結構加載在狀態(tài)驅動的代碼上的方法 : 他使用表將輸入映射到狀態(tài)轉換犯助。對每一個狀態(tài) , 一張表將每一個可能的輸入映射到一個后繼狀態(tài)癣漆。實際上 , 這種方法將條件代碼 (和State模式下的虛函數)映射為一個查找表。
表的主要好處是它們的規(guī)則性 : 你可以通過更改數據而不是更改程序代碼來改變狀態(tài)轉換的準則剂买。然而它也有一些缺點:- 對表的查找通常不如(虛)函數調用效率高扑媚;
- 用統(tǒng)一的、表格的形式表示轉換邏輯使得轉換準則變得不夠明確而難以理解雷恃;
- 通常難以加入伴隨狀態(tài)轉換的一些動作。表驅動的方法描述了狀態(tài)和它們之間的轉換费坊,但必須擴充這個機制以便在每一個轉換上能夠進行任意的計算倒槐。
表驅動的狀態(tài)機和State模式的主要區(qū)別可以被總結如下: State模式對與狀態(tài)相關的行為進行建模, 而表驅動的方法著重于定義狀態(tài)轉換。
- 3 創(chuàng)建和銷毀State對象:一個常見的值得考慮的實現(xiàn)上的權衡是 , 究竟是( 1 )僅當需要State對象時才創(chuàng)建它們并隨后銷毀它們附井,還是 ( 2 )提前創(chuàng)建它們并且始終不銷毀它們讨越。
當將要進入的狀態(tài)在運行時是不可知的 , 并且上下文不經常改變狀態(tài)時 , 第一種選擇較為可取两残。這種方法避免創(chuàng)建不會被用到的對象 , 如果State對象存儲大量的信息時這一點很重要。當狀態(tài)改變很頻繁時, 第二種方法較好把跨。在這種情況下最好避免銷毀狀態(tài) , 因為可能很快再次需要用到它們人弓。此時可以預先一次付清創(chuàng)建各個狀態(tài)對象的開銷 , 并且在運行過程中根本不存在銷毀狀態(tài)對象的開銷。但是這種方法可能不太方便 , 因為Context必須保存對所有可能會進入的那些狀態(tài)的引用着逐。 - 4 使用動態(tài)繼承:改變一個響應特定請求的行為可以用在運行時刻改變這個對象的類的辦法實現(xiàn), 但這在大多數面向對象程序設計語言中都是不可能的崔赌。 Self 和其他一些基于委托的語言卻是例外,它們提供這種機制 , 從而直接支持State模式耸别。Self 中的對象可將操作委托給其他對象以達到某種形式的動態(tài)繼承健芭。在運行時刻改變委托的目標有效地改變了繼承的結構。這一機制允許對象改變它們的行為秀姐,也就是改變它們的類慈迈。