一、引子 狀態(tài)模式自身結(jié)構(gòu)非常簡(jiǎn)單——前面剛剛介紹了幾個(gè)結(jié)構(gòu)比較簡(jiǎn)單的設(shè)計(jì)模式,和他們一樣,狀態(tài)模式在具體實(shí)現(xiàn)上留下了可變換的余地。我前面已經(jīng)介紹過它的孿生兄妹策略模式了,大家可以兩者比較著閱讀。本文將會(huì)討論兩者的區(qū)別。
二、定義與結(jié)構(gòu) GOF《設(shè)計(jì)模式》中給狀態(tài)模式下的定義為:允許一個(gè)對(duì)象在其內(nèi)部狀態(tài)改變時(shí)改變它的行為。這個(gè)對(duì)象看起來似乎修改了它的類。看起來,狀態(tài)模式好像是神通廣大——居然能夠“修改自身的類”!
能夠讓程序根據(jù)不同的外部情況來做出不同的響應(yīng),最直接的方法就是在程序中將這些可能發(fā)生的外部情況全部考慮到,使用if else語句來進(jìn)行代碼響應(yīng)選擇。但是這種方法對(duì)于復(fù)雜一點(diǎn)的狀態(tài)判斷,就會(huì)顯得雜亂無章,容易產(chǎn)生錯(cuò)誤;而且增加一個(gè)新的狀態(tài)將會(huì)帶來大量的修改。這個(gè)時(shí)候“能夠修改自身”的狀態(tài)模式的引入也許是個(gè)不錯(cuò)的主意。
狀態(tài)模式可以有效的替換充滿在程序中的if else語句:將不同條件下的行為封裝在一個(gè)類里面,再給這些類一個(gè)統(tǒng)一的父類來約束他們。來看一下狀態(tài)模式的角色組成吧:
1) 使用環(huán)境(Context)角色:客戶程序是通過它來滿足自己的需求。它定義了客戶程序需要的接口;并且維護(hù)一個(gè)具體狀態(tài)角色的實(shí)例,這個(gè)實(shí)例來決定當(dāng)前的狀態(tài)。
2) 狀態(tài)(State)角色:定義一個(gè)接口以封裝與使用環(huán)境角色的一個(gè)特定狀態(tài)相關(guān)的行為。
3) 具體狀態(tài)(Concrete State)角色:實(shí)現(xiàn)狀態(tài)角色定義的接口。
類圖如下,結(jié)構(gòu)非常簡(jiǎn)單也與策略模式非常相似。
三、實(shí)現(xiàn) 由于狀態(tài)模式結(jié)構(gòu)非常簡(jiǎn)單,所以在這里羅列一些反映狀態(tài)模式實(shí)現(xiàn)結(jié)構(gòu)的代碼沒有什么太大的作用。如果你有興趣的話可以按照上面類圖來編寫一下。
在引子中已經(jīng)提到,狀態(tài)模式在具體實(shí)現(xiàn)上存在不同的方案。因此這里重點(diǎn)就這些不同的實(shí)現(xiàn)方式進(jìn)行介紹和討論。
首先,實(shí)現(xiàn)時(shí)是否將狀態(tài)角色、具體狀態(tài)角色暴露給客戶程序?按照GOF的建議是不希望將狀態(tài)角色暴露給客戶程序的,與客戶程序打交道的僅僅是使用環(huán)境角色,客戶是不知道系統(tǒng)是怎么實(shí)現(xiàn)的,更不關(guān)心什么有幾個(gè)具體狀態(tài)。但是當(dāng)使用環(huán)境角色中的初始狀態(tài)緊緊依賴于客戶程序時(shí),適乎暴露是在所難免的——這就與策略模式異常相似了!
具體狀態(tài)角色中的行為一般是與使用環(huán)境角色密切相關(guān)的。因此這里便有了一個(gè)小細(xì)節(jié):我們把使用環(huán)境角色作為參數(shù)傳遞進(jìn)入具體狀態(tài)角色后,是在具體狀態(tài)角色中來實(shí)現(xiàn)狀態(tài)響應(yīng)行為;還是僅僅調(diào)用在使用環(huán)境角色中已經(jīng)實(shí)現(xiàn)了的方法?由于這些行為往往與使用環(huán)境角色相關(guān),所以按照《重構(gòu)》一書的“指導(dǎo)”——后一種實(shí)現(xiàn)方法是比較地道的。
從定義可知,狀態(tài)模式是要應(yīng)對(duì)狀態(tài)轉(zhuǎn)換的。那么狀態(tài)的轉(zhuǎn)換在哪里定義呢?你可以選擇在使用環(huán)境角色的代碼中來表現(xiàn)出來,當(dāng)然這便意味著狀態(tài)轉(zhuǎn)變的規(guī)則就固定下來了。GOF還給出了另外一種稍微靈活一點(diǎn)的實(shí)現(xiàn)方式:在每一個(gè)具體狀態(tài)角色中來指定后續(xù)狀態(tài)以及何時(shí)進(jìn)行轉(zhuǎn)換。
其實(shí)在java強(qiáng)大的反射機(jī)制的支持下,我們還可以將狀態(tài)的轉(zhuǎn)換做的更加靈活——我們可以將狀態(tài)轉(zhuǎn)換的規(guī)則寫在.xml等等的配置文件里面甚至是數(shù)據(jù)庫中,我們姑且叫做狀態(tài)轉(zhuǎn)換表。進(jìn)行轉(zhuǎn)換前,根據(jù)狀態(tài)轉(zhuǎn)換表來讀取下一個(gè)狀態(tài),然后利用反射獲得具體的狀態(tài)對(duì)象……??雌饋砗懿诲e(cuò)的樣子,只是效率可能低一些,在企業(yè)應(yīng)用中這應(yīng)該不是最重要的。
狀態(tài)模式已經(jīng)被我們想象著“實(shí)現(xiàn)”了一番。那么狀態(tài)模式的引入會(huì)給我們的程序帶來哪些優(yōu)勢(shì)呢?前面我們已經(jīng)說過:狀態(tài)模式的引入免除了代碼中復(fù)雜而庸長(zhǎng)的邏輯判斷語句。而且具體狀態(tài)角色將具體狀態(tài)和它對(duì)應(yīng)的行為封裝了起來,這使得增加一種新的狀態(tài)變得簡(jiǎn)單一些。而且如果設(shè)計(jì)合理得話,具體狀態(tài)角色可以被重用(和策略模式一樣,可以考慮使用享元模式來實(shí)現(xiàn))。
使用狀態(tài)模式也會(huì)帶來一些問題。每個(gè)狀態(tài)對(duì)應(yīng)一個(gè)具體的狀態(tài)類,使得整體分散,邏輯不太清晰。當(dāng)然對(duì)于一個(gè)狀態(tài)非常多的系統(tǒng),狀態(tài)模式帶來的優(yōu)點(diǎn)還是大于它的缺點(diǎn)的。
由上面的分析就可以很明確的知道什么時(shí)候該使用狀態(tài)模式了。下面是GOF在《設(shè)計(jì)模式》中給出的狀態(tài)模式的適用情況:
1) 一個(gè)對(duì)象的行為取決于它的狀態(tài), 并且它必須在運(yùn)行時(shí)刻根據(jù)狀態(tài)改變它的行為。
2) 一個(gè)操作中含有龐大的多分支的條件語句,且這些分支依賴于該對(duì)象的狀態(tài)。
四、狀態(tài)VS策略 仔細(xì)對(duì)比狀態(tài)模式和策略模式,難免會(huì)產(chǎn)生疑問:這兩個(gè)明明是一個(gè)東西嘛!下面我們就來分析下兩者區(qū)別。
首先我要聲明,在實(shí)際應(yīng)用中只要能夠使得你的代碼靈活漂亮起來,何必計(jì)較這些方方面面的差別呢?
BrandonGoldfedder在《模式的樂趣》里是怎么說的:“strategy模式在結(jié)構(gòu)上與state模式非常相似,但是在概念上,他們的目的差異非常大。區(qū)分這兩個(gè)模式的關(guān)鍵是看行為是由狀態(tài)驅(qū)動(dòng)還是由一組算法驅(qū)動(dòng),這條規(guī)則似乎有點(diǎn)隨意,但是在判斷時(shí)還是需要考慮它。通常,State模式的“狀態(tài)”是在對(duì)象內(nèi)部的,Strategy模式的“策略”可以在對(duì)象外部,不過這也不是一條嚴(yán)格、可靠的規(guī)則。”
我很同意Brandon Goldfedder的觀點(diǎn)。這兩個(gè)模式的劃分,就在于使用的目的是不同的——策略模式用來處理算法變化,而狀態(tài)模式則是處理狀態(tài)變化(好玄乎阿)。
策略模式中,算法是否變化完全是由客戶程序開決定的,而且往往一次只能選擇一種算法,不存在算法中途發(fā)生變化的情況。從《深入淺出策略模式》中的例子可以很好的看出。
而狀態(tài)模式如定義中所言,在它的生命周期中存在著狀態(tài)的轉(zhuǎn)變和行為得更改,而且狀態(tài)變化是一個(gè)線性的整體;對(duì)于客戶程序來言,這種狀態(tài)變化往往是透明的。
using System;
namespace DesignPattern.State
{
/**//**//**////
/// 抽象狀態(tài) 狀態(tài)接口
///
public interface ITcpState
{
void Open();
void Close();
void Acknowledge();
}
/**//**//**////
/// 此類相當(dāng)于Context
///
public class TcpConnection
{
private ITcpState state;
public ITcpState State
{
set{this.state=value;}
}
public void Open()
{
this.State=new TcpEstablished();
this.state.Open();
this.State=new TcpListen();
}
public void Close()
{
this.state.Close();
this.State=new TcpClosed();
}
public void Acknowledge()
{
this.state.Acknowledge();
}
}
public class TcpEstablished : ITcpState
{
public void Open()
{
Console.WriteLine("Have opened,can not open again!");
}
public void Close()
{
Console.WriteLine("Closing");
}
public void Acknowledge()
{
Console.WriteLine("TcpEstablished!");
}
}
public class TcpListen : ITcpState
{
public void Open()
{
Console.WriteLine("Have opened,can not open again!");
}
public void Close()
{
Console.WriteLine("Closing");
}
public void Acknowledge()
{
Console.WriteLine("TcpListen!");
}
}
public class TcpClosed : ITcpState
{
public void Open()
{
Console.WriteLine("Openning");
}
public void Close()
{
Console.WriteLine("Have closed,can not close again!");
}
public void Acknowledge()
{
Console.WriteLine("TcpClosed!");
}
}
public class Client
{
public static void Main()
{
TcpConnection tcpcon=new TcpConnection();
tcpcon.Open();
tcpcon.Acknowledge();
tcpcon.Open();
tcpcon.Close();
tcpcon.Acknowledge();
tcpcon.Close();
}
}
}
五、總結(jié) 比較籠統(tǒng)地介紹了下狀態(tài)模式,并將它和非常相近的策略模式進(jìn)行了比較。歡迎大家學(xué)習(xí)指正。