Java設(shè)計模式之java狀態(tài)模式詳解
狀態(tài)模式的結(jié)構(gòu)
用一句話來表述,狀態(tài)模式把所研究的對象的行為包裝在不同的狀態(tài)對象里,每一個狀態(tài)對象都屬于一個抽象狀態(tài)類的一個子類。狀態(tài)模式的意圖是讓一個對象在其內(nèi)部狀態(tài)改變的時候,其行為也隨之改變。狀態(tài)模式的示意性類圖如下所示:

狀態(tài)模式的角色
- 環(huán)境(Context)角色,也成上下文:定義客戶端所感興趣的接口,并且保留一個具體狀態(tài)類的實例。這個具體狀態(tài)類的實例給出此環(huán)境對象的現(xiàn)有狀態(tài)。
- 抽象狀態(tài)(State)角色:定義一個接口,用以封裝環(huán)境(Context)對象的一個特定的狀態(tài)所對應(yīng)的行為。
- 具體狀態(tài)(ConcreteState)角色:每一個具體狀態(tài)類都實現(xiàn)了環(huán)境(Context)的一個狀態(tài)所對應(yīng)的行為。
示例代碼
環(huán)境角色類
public class Context {
//持有一個State類型的對象實例
private State state;
public void setState(State state) {
this.state = state;
}
/**
* 用戶感興趣的接口方法
*/
public void request(String sampleParameter) {
//轉(zhuǎn)調(diào)state來處理
state.handle(sampleParameter);
}
}
抽象狀態(tài)類
public interface State {
/**
* 狀態(tài)對應(yīng)的處理
*/
public void handle(String sampleParameter);
}
具體狀態(tài)類
public class ConcreteStateA implements State {
@Override
public void handle(String sampleParameter) {
System.out.println("ConcreteStateA handle :" + sampleParameter);
}
}
public class ConcreteStateB implements State {
@Override
public void handle(String sampleParameter) {
System.out.println("ConcreteStateB handle :" + sampleParameter);
}
}
客戶端類
public class Client {
public static void main(String[] args){
//創(chuàng)建狀態(tài)
State state = new ConcreteStateB();
//創(chuàng)建環(huán)境
Context context = new Context();
//將狀態(tài)設(shè)置到環(huán)境中
context.setState(state);
//請求
context.request("test");
}
}
從上面可以看出,環(huán)境類Context的行為request()是委派給某一個具體狀態(tài)類的。通過使用多態(tài)性原則,可以動態(tài)改變環(huán)境類Context的屬性State的內(nèi)容,使其從指向一個具體狀態(tài)類變換到指向另一個具體狀態(tài)類,從而使環(huán)境類的行為request()由不同的具體狀態(tài)類來執(zhí)行。
適用場景
(1)對象的行為依賴于它的狀態(tài),并且可以在運(yùn)行時根據(jù)狀態(tài)改變行為。
(2) 代碼中包含大量與對象狀態(tài)有關(guān)的條件語句:一個操作中含有龐大的多分支的條件(if else(或switch case)語句,且這些分支依賴于該對象的狀態(tài)。這個狀態(tài)通常用一個或多個枚舉常量表示。通常 , 有多個操作包含這一相同的條件結(jié)構(gòu)。 State模式將每一個條件分支放入一個獨(dú)立的類中。這使得你可以根據(jù)對象自身的情況將對象的狀態(tài)作為一個對象,這一對象可以不依賴于其他對象而獨(dú)立變化。
投票案例
考慮一個在線投票系統(tǒng)的應(yīng)用,要實現(xiàn)控制同一個用戶只能投一票,如果一個用戶反復(fù)投票,而且投票次數(shù)超過5次,則判定為惡意刷票,要取消該用戶投票的資格,當(dāng)然同時也要取消他所投的票;如果一個用戶的投票次數(shù)超過8次,將進(jìn)入黑名單,禁止再登錄和使用系統(tǒng)。
要使用狀態(tài)模式實現(xiàn),首先需要把投票過程的各種狀態(tài)定義出來,根據(jù)以上描述大致分為四種狀態(tài):正常投票、反復(fù)投票、惡意刷票、進(jìn)入黑名單。然后創(chuàng)建一個投票管理對象(相當(dāng)于Context)。
系統(tǒng)的結(jié)構(gòu)圖如下所示:

抽象狀態(tài)類
//抽象狀態(tài)類
public interface VoteState
{
/**
* 處理狀態(tài)對應(yīng)的行為
* @param user 投票人
* @param voteItem 投票項
* @param voteManager 投票上下文,用來在實現(xiàn)狀態(tài)對應(yīng)的功能處理的時候,
* 可以回調(diào)上下文的數(shù)據(jù)
*/
public void vote(String user,String voteItem,VoteManager voteManager);
}
具體狀態(tài)類——正常投票
//具體狀態(tài)類---正常投票
public class NormalVoteState implements VoteState
{
@Override
public void vote(String user, String voteItem, VoteManager voteManager) {
//正常投票,記錄到投票記錄中
voteManager.getMapVote().put(user, voteItem);
System.out.println("恭喜投票成功");
}
}
具體狀態(tài)類–重復(fù)投票
//具體狀態(tài)類--重復(fù)投票
public class RepeatVoteState implements VoteState
{
@Override
public void vote(String user, String voteItem, VoteManager voteManager) {
//重復(fù)投票,暫時不做處理
System.out.println("請不要重復(fù)投票");
}
}
具體狀態(tài)類—惡意投票
//惡意刷票
public class SpiteVoteState implements VoteState{
@Override
public void vote(String user, String voteItem, VoteManager voteManager) {
// 惡意投票,取消用戶的投票資格,并取消投票記錄
String str = voteManager.getMapVote().get(user);
if(str != null){
voteManager.getMapVote().remove(user);
}
System.out.println("你有惡意刷屏行為,取消投票資格");
}
}
具體狀態(tài)類–黑名單
//黑名單
public class BlackVoteState implements VoteState
{
@Override
public void vote(String user, String voteItem, VoteManager voteManager) {
//記錄黑名單中,禁止登錄系統(tǒng)
System.out.println("進(jìn)入黑名單,將禁止登錄和使用本系統(tǒng)");
}
}
環(huán)境類
//環(huán)境類
public class VoteManager
{
//維護(hù)一個抽象狀態(tài)對象
private VoteState state;
//記錄用戶投票的結(jié)果,Map<String,String>對應(yīng)Map<用戶名稱,投票的選項>
private Map<String,String> mapVote = new HashMap<String,String>();
//記錄用戶投票次數(shù),Map<String,Integer>對應(yīng)Map<用戶名稱,投票的次數(shù)>
private Map<String,Integer> mapVoteCount = new HashMap<String,Integer>();
/**
* 獲取用戶投票結(jié)果的Map
*/
public Map<String, String> getMapVote() {
return mapVote;
}
/**
* 投票
* @param user 投票人
* @param voteItem 投票的選項
*/
public void vote(String user,String voteItem){
//1.為該用戶增加投票次數(shù)
//從記錄中取出該用戶已有的投票次數(shù)
Integer oldVoteCount = mapVoteCount.get(user);
if(oldVoteCount == null){
oldVoteCount = 0;
}
oldVoteCount += 1;
mapVoteCount.put(user, oldVoteCount);
//2.判斷該用戶的投票類型,就相當(dāng)于判斷對應(yīng)的狀態(tài)
//到底是正常投票、重復(fù)投票、惡意投票還是上黑名單的狀態(tài)
if(oldVoteCount == 1){
state = new NormalVoteState();
}
else if(oldVoteCount > 1 && oldVoteCount < 5){
state = new RepeatVoteState();
}
else if(oldVoteCount >= 5 && oldVoteCount <8){
state = new SpiteVoteState();
}
else if(oldVoteCount > 8){
state = new BlackVoteState();
}
//然后轉(zhuǎn)調(diào)狀態(tài)對象來進(jìn)行相應(yīng)的操作
state.vote(user, voteItem, this);
}
}
客戶端測試
public class Client
{
public static void main(String[] args) {
VoteManager vm = new VoteManager();
for(int i=0;i<9;i++){
vm.vote("u1","A");
}
}
}

從上面的示例可以看出,狀態(tài)的轉(zhuǎn)換基本上都是內(nèi)部行為,主要在狀態(tài)模式內(nèi)部來維護(hù)。比如對于投票的人員,任何時候他的操作都是投票,但是投票管理對象的處理卻不一定一樣,會根據(jù)投票的次數(shù)來判斷狀態(tài),然后根據(jù)狀態(tài)去選擇不同的處理。
認(rèn)識狀態(tài)模式
狀態(tài)和行為
- 所謂對象的狀態(tài),通常指的就是對象實例的屬性的值;而行為指的就是對象的功能,再具體點說,行為大多可以對應(yīng)到方法上。
- 狀態(tài)模式的功能就是分離狀態(tài)的行為,通過維護(hù)狀態(tài)的變化,來調(diào)用不同狀態(tài)對應(yīng)的不同功能。也就是說,狀態(tài)和行為是相關(guān)聯(lián)的,它們的關(guān)系可以描述為:狀態(tài)決定行為。
- 由于狀態(tài)是在運(yùn)行期被改變的,因此行為也會在運(yùn)行期根據(jù)狀態(tài)的改變而改變。
行為的平行性
注意平行線而不是平等性。所謂平行性指的是各個狀態(tài)的行為所處的層次是一樣的,相互獨(dú)立的、沒有關(guān)聯(lián)的,是根據(jù)不同的狀態(tài)來決定到底走平行線的哪一條。行為是不同的,當(dāng)然對應(yīng)的實現(xiàn)也是不同的,相互之間是不可替換的。

而平等性強(qiáng)調(diào)的是可替換性,大家是同一行為的不同描述或?qū)崿F(xiàn),因此在同一個行為發(fā)生的時候,可以根據(jù)條件挑選任意一個實現(xiàn)來進(jìn)行相應(yīng)的處理。

大家可能會發(fā)現(xiàn)狀態(tài)模式的結(jié)構(gòu)和策略模式的結(jié)構(gòu)完全一樣,但是,它們的目的、實現(xiàn)、本質(zhì)卻是完全不一樣的。還有行為之間的特性也是狀態(tài)模式和策略模式一個很重要的區(qū)別,狀態(tài)模式的行為是平行性的,不可相互替換的;而策略模式的行為是平等性的,是可以相互替換的。
環(huán)境和狀態(tài)處理對象
- 在狀態(tài)模式中,環(huán)境(Context)是持有狀態(tài)的對象,但是環(huán)境(Context)自身并不處理跟狀態(tài)相關(guān)的行為,而是把處理狀態(tài)的功能委托給了狀態(tài)對應(yīng)的狀態(tài)處理類來處理。
- 在具體的狀態(tài)處理類中經(jīng)常需要獲取環(huán)境(Context)自身的數(shù)據(jù),甚至在必要的時候會回調(diào)環(huán)境(Context)的方法,因此,通常將環(huán)境(Context)自身當(dāng)作一個參數(shù)傳遞給具體的狀態(tài)處理類。
- 客戶端一般只和環(huán)境(Context)交互??蛻舳丝梢杂脿顟B(tài)對象來配置一個環(huán)境(Context),一旦配置完畢,就不再需要和狀態(tài)對象打交道了。客戶端通常不負(fù)責(zé)運(yùn)行期間狀態(tài)的維護(hù),也不負(fù)責(zé)決定后續(xù)到底使用哪一個具體的狀態(tài)處理對象。
狀態(tài)模式優(yōu)點
- 每個狀態(tài)都是一個子類,只要增加狀態(tài)就要增加子類,修改狀態(tài),只修改一個子類即可。
- 結(jié)構(gòu)清晰,避免了過多的switch…case或者if…else語句的使用,避免了程序的復(fù)雜性,提高可維護(hù)性。
- State對象可被共享如果State對象沒有實例變量—即它們表示的狀態(tài)完全以它們的類型來編碼—那么各Context對象可以共享一個State對象。當(dāng)狀態(tài)以這種方式被共享時,它們必然是沒有內(nèi)部狀態(tài), 只有行為的輕量級對象。
狀態(tài)模式的缺點
- 狀態(tài)模式的使用必然會增加系統(tǒng)類和對象的個數(shù)。
- 狀態(tài)模式的結(jié)構(gòu)與實現(xiàn)都較為復(fù)雜,如果使用不當(dāng)將導(dǎo)致程序結(jié)構(gòu)和代碼的混亂。
狀態(tài)模式和策略模式對比
相同之處:
- 在狀態(tài)模式和策略模式中,Context對象對修改是關(guān)閉的,添加新的狀態(tài)或策略,都不需要修改Context。
- 正如狀態(tài)模式中的Context會有初始狀態(tài)一樣,策略模式同樣有默認(rèn)策略。
- 狀態(tài)模式以不同的狀態(tài)封裝不同的行為,而策略模式以不同的策略封裝不同的行為。
- 它們都依賴子類去實現(xiàn)相關(guān)行為
不同之處:
最根本的差異在于策略模式是在求解同一個問題的多種解法,這些不同解法之間毫無關(guān)聯(lián);狀態(tài)模式則不同,狀態(tài)模式要求各個狀態(tài)之間有所關(guān)聯(lián),以便實現(xiàn)狀態(tài)轉(zhuǎn)移。
參考文章
總結(jié)
本篇文章就到這里了,希望能夠給你帶來幫助,也希望您能夠多多關(guān)注腳本之家的更多內(nèi)容!
相關(guān)文章
詳解Spring Security 中的四種權(quán)限控制方式
這篇文章主要介紹了詳解Spring Security 中的四種權(quán)限控制方式,文中通過示例代碼介紹的非常詳細(xì),對大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價值,需要的朋友們下面隨著小編來一起學(xué)習(xí)學(xué)習(xí)吧2020-10-10
Java使用Jdbc連接Oracle執(zhí)行簡單查詢操作示例
這篇文章主要介紹了Java使用Jdbc連接Oracle執(zhí)行簡單查詢操作,結(jié)合實例形式詳細(xì)分析了java基于jdbc實現(xiàn)Oracle數(shù)據(jù)庫的連接與查詢相關(guān)操作技巧,需要的朋友可以參考下2019-09-09
Java的MyBatis+Spring框架中使用數(shù)據(jù)訪問對象DAO模式的方法
Data Access Object數(shù)據(jù)訪問對象模式在Java操作數(shù)據(jù)庫部分的程序設(shè)計中經(jīng)常被使用到,這里我們就來看一下Java的MyBatis+Spring框架中使用數(shù)據(jù)訪問對象DAO模式的方法:2016-06-06
使用java + selenium + OpenCV破解騰訊防水墻滑動驗證碼功能
這篇文章主要介紹了使用java + selenium + OpenCV破解騰訊防水墻滑動驗證碼,本文通過實例代碼給大家介紹的非常詳細(xì),對大家的學(xué)習(xí)或工作具有一定的參考借鑒價值,需要的朋友可以參考下2020-11-11
服務(wù)器實現(xiàn)Java遠(yuǎn)程訪問Linux服務(wù)器方式(JSch)
文章介紹了如何使用Java遠(yuǎn)程訪問Linux服務(wù)器,主要包括建立SSH連接、使用JSch庫執(zhí)行命令、解析返回值以及關(guān)閉連接的步驟2024-11-11
SpringBoot用JdbcTemplates操作Mysql實例代碼詳解
JdbcTemplate是Spring框架自帶的對JDBC操作的封裝,目的是提供統(tǒng)一的模板方法使對數(shù)據(jù)庫的操作更加方便、友好,效率也不錯,這篇文章主要介紹了SpringBoot用JdbcTemplates操作Mysql2022-10-10

