狀態模式

1.狀態模式介紹

         狀態模式中行爲是由狀態來決定的,不同的狀態下有不同的行爲。狀態模式和策略模式的結構幾乎完全一樣,但它們的目的、本質卻完全不一樣。狀態模式的行爲是平行的、不可替換的,策略模式的行爲是彼此獨立、可相互替換的。用一句話來表述,狀態模式把對象的行爲包裝在不同的狀態對象裏,每一個狀態對象都有一個共同的抽象狀態基類。狀態模式的意圖是讓一個對象在其內部狀態改變的時候,其行爲也隨之改變。

2.狀態模式的定義

當一個對象的內在狀態改變時允許改變其行爲,這個對象看起來像是改變了其類。

3.狀態模式的使用場景

  1. 一個對象的行爲取決於它的狀態,並且它必須在運行時根據狀態改變它的行爲。
  2. 代碼中包含大量與對象狀態有關的條件語句,例如,一個操作中含有龐大的多分支語句(if-else或switch-case),且這些分支依賴於該對象的狀態。
             狀態模式將每一個條件分支放入一個獨立的類中,這使得你可以根據對象自身情況將對象的狀態作爲一個對象,這一對象可以不依賴於其它對象而獨立變化,這樣通過多態來去除多的、重複的if-else等分支語句。

4.狀態模式的UML圖

UML類圖如下:
狀態模式UML圖
角色介紹:

  • Context:環境類,定義客戶感興趣的接口,維護一個State子類的實例,這個實例定義了對象的當前狀態。
  • State:抽象狀態類或者狀態接口,定義一個或者一組接口,表示該狀態下的行爲。
  • ConcreteStateA、ConcreteStateB:具體狀態類,每一個具體的狀態類實現抽象State中定義的接口,從而大道不同狀態下的不同行爲。

5.狀態模式的簡單示例

         下面我們就以電視遙控器爲例來演示一下狀態模式的實現。我們首先將電視的狀態簡單分爲開機狀態和關機狀態,在開機狀態下可以通過遙控器進行頻道切換、調整音量等操作,但是,此時重複按開機鍵是無效的;而在關機狀態下,頻道切換、調整音量、關機都是無效的操作,只有按開機按鈕時會生效。也就是說電視的內部狀態決定了遙控器的行爲,我們看看第一版實現:

/*
 * 電視遙控器,含有開機、關機、下一頻道、上一頻道、調高音量、調低音量這幾個功能
 */
public class TvController {
	//開機狀態
	private final static int POWER_ON=1;
	//關機狀態
	private final static int POWER_OFF=2;
	private int mState=POWER_OFF;
	public void powerOn(){
		mState=POWER_ON;
		if (mState==POWER_OFF) {
			System.out.println("開機了");
		}
	}
	
	public void powerOff(){
		mState=POWER_OFF;
		if (mState==POWER_ON) {
			System.out.println("關機啦");
		}
	}
	
	public void nextChannel(){
		if (mState==POWER_ON) {
			System.out.println("下一個頻道");
		}else{
			System.out.println("兩個紅燈提示沒有開機");
		}
	}
	
	public void prevChannel(){
		if (mState==POWER_ON) {
			System.out.println("上一個頻道");
		}else{
			System.out.println("兩個紅燈提示沒有開機");
		}
	}
	
	public void turnUp(){
		if (mState==POWER_ON) {
			System.out.println("調高音量");
		}else{
			System.out.println("兩個紅燈提示沒有開機");
		}
	}
	
	public void turnDown(){
		if (mState==POWER_ON) {
			if (mState==POWER_ON) {
				System.out.println("調低音量");
			}else{
				System.out.println("兩個紅燈提示沒有開機");
			}
		}
	}

}

         可以看到,在TvController類中,通過mState字段存儲了電視的狀態,並且在各個操作中根據狀態來判斷是否應該執行。這就導致你了在每個功能中都需要使用if-else,代碼重複、相對較爲混亂,這是在只有兩個狀態和簡單幾個功能函數的情況下,那麼當狀態變成5個、功能函數變爲10個呢?每個函數中都要用if-else進行判斷,而這些代碼都充斥在一個類中,這些重複的代碼無法被提取出來,這使得這個類變得越來越難以維護。
         狀態模式就是爲了解決這類問題而出現的,我們將這些狀態用對象來代替,將這些行爲封裝到對象中,使得在不同的狀態下有不同的實現,這樣就將這些if-else從TvController類中去掉,整個結構也變得清晰起來。

//電視狀態接口,定義了電視操作的函數
public interface TvState {
    public void nextChannel();
    public void prevChannel();
    public void turnUp();
    public void turnDown();
    
}


//關機狀態,此時只有開機功能是有效的
public class PowerOffState implements TvState{

   @Override
   public void nextChannel() {
   	
   }

   @Override
   public void prevChannel() {
   	
   }

   @Override
   public void turnUp() {
   	
   }

   @Override
   public void turnDown() {
   	
   }

}


//開機狀態,此時再觸發開機功能不做任何操作
public class PowerOnState implements TvState {

   @Override
   public void nextChannel() {
   	System.out.println("下一頻道");
   }

   @Override
   public void prevChannel() {
   	System.out.println("上一頻道");
   }

   @Override
   public void turnUp() {
   	System.out.println("調高音量");
   }

   @Override
   public void turnDown() {
   	System.out.println("調低音量");
   }

}

//電源操作接口
public interface PowerController {
   public void powerOn();

   public void powerOff();
}

//電視遙控器,類似於經典狀態模式中的Context
public class TvController implements PowerController {
   TvState mTvState;

   public void setmTvState(TvState mTvState) {
   	this.mTvState = mTvState;
   }

   @Override
   public void powerOn() {
   	setmTvState(new PowerOnState());
   	System.out.println("開機啦");
   }

   @Override
   public void powerOff() {
   	setmTvState(new PowerOffState());
   	System.out.println("關機啦");
   }

   public void nextChannel() {
   	mTvState.nextChannel();
   }

   public void prevChannel() {
   	mTvState.prevChannel();
   }

   public void turnUp() {
   	mTvState.turnUp();
   }

   public void turnDown() {
   	mTvState.turnDown();
   }
}

下面是客戶端調用的代碼;

public class Client {
	public static void main(String[] args) {
           TvController tvController=new TvController();
           //設置開機狀態
           tvController.powerOn();
           //下一個頻道
           tvController.nextChannel();
           //調高音量
           tvController.turnUp();
           //設置關機
           tvController.powerOff();
           //調高音量,此時不會生效
           tvController.turnUp();
	}
}

輸出結果如下:

開機啦
下一頻道
調高音量
關機啦

        上述實現中,我們抽象了一個TvState接口,該接口中有操作電視的所有函數,該接口有兩個實現類,即開機狀態(PowerOnState)和關機狀態(PowerOffState)。開機狀態下只有開機功能是無效的,也就是說在已經開機的時候用戶在按下開機鍵不會產生任何反應;而在關機狀態下,只有開機功能是可用的,其它功能都不會生效。同一個操作,如調高音量的turnUp函數,在關機狀態下無效,在開機狀態下就會將電視的音量調高,也就是說電視的內部狀態影響了電視遙控器的行爲。狀態模式將這些行爲封裝到狀態類中,在進行操作時將這些功能轉發給狀態對象,不同的狀下有不同的實現,這樣就通過多態的形式去除了重複、雜亂的if-else語句,這也是狀態模式的精髓所在。

6.狀態模式實戰

        在開發過程中,我們用到狀態模式最常見的地方應該是用戶登錄系統。在用戶已登錄和未登錄的情況下,對於同一事件的處理行爲是不一樣的,例如,在新浪微博中,用戶在未登錄的情況下點擊轉發按鈕,此時會先讓用戶登錄,然後再執行操作;如果是已登錄的情況下,那麼用戶輸入轉發的內容就可以直接進行轉發。

總結

        狀態模式的關鍵點在於不同的狀態下對於同一行爲有不同的響應,這其實就是一個將if-else用多態來實現的一個具體示例。在if-else或者switch-case形式下根據不同的狀態進行判斷,如果是狀態A那麼執行方法A、狀態B執行方法B,但這種實現使得邏輯耦合在一起,易於出錯,通過狀態模式能夠很好地消除這類“醜陋”的邏輯處理,當然並不是任何出現if-else的地方都應該通過狀態模式重構,模式的運用一定要考慮所處的情景以及你要解決的問題,只有符合特定的場景才建議使用對應的模式。
狀態模式的優點:
        State模式將所有與一個特定的狀態相關的行爲都放入一個狀態對象中,它提供了一個更好的方法來組織與特定狀態相關的代碼,將繁瑣的狀態轉換成結構清晰的狀態類族,在避免代碼膨脹的同時也保證了可擴展性與可維護性。
狀態模式的缺點:
        狀態模式的使用必然會增加系統類和對象個數。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章