原文地址 折騰Java設計模式之狀態模式
狀態模式
在狀態模式(State Pattern)中,類的行爲是基於它的狀態改變的。這種類型的設計模式屬於行爲型模式。在狀態模式中,我們創建表示各種狀態的對象和一個行爲隨着狀態對象改變而改變的 context 對象。通俗點就是一個對象在內部狀態發生改變時改變它的行爲。
介紹
意圖 允許對象在內部狀態發生改變時改變它的行爲,對象看起來好像修改了它的類。
主要解決 對象的行爲依賴於它的狀態(屬性),並且可以根據它的狀態改變而改變它的相關行爲。
何時使用 代碼中包含大量與對象狀態有關的條件語句。
如何解決 將各種具體的狀態類抽象出來。
關鍵代碼 通常命令模式的接口中只有一個方法。而狀態模式的接口中有一個或者多個方法。而且,狀態模式的實現類的方法,一般返回值,或者是改變實例變量的值。也就是說,狀態模式一般和對象的狀態有關。實現類的方法有不同的功能,覆蓋接口中的方法。狀態模式和命令模式一樣,也可以用於消除 if...else 等條件選擇語句。
UML圖
主要角色
1)Context(環境類):環境類擁有各種不同狀態的對象,作爲外部使用的接口,負責調用狀態類接口。
2)State(抽象狀態):抽象狀態既可以爲抽象類,也可以直接定義成接口。主要用於定義狀態抽象方法,具體實現由子類負責。
3)ConcreteState(具體狀態類):具體狀態類爲抽象狀態的實現者,不同的狀態類對應這不同的狀態,其內部實現也不相同。環境類中使用不同狀態的對象時,能實現不同的處理邏輯
應用實例
1、打籃球的時候運動員可以有正常狀態、不正常狀態和超常狀態。
2、曾侯乙編鐘中,'鍾是抽象接口','鍾A'等是具體狀態,'曾侯乙編鐘'是具體環境(Context)。
優點
1、封裝了轉換規則。
2、枚舉可能的狀態,在枚舉狀態之前需要確定狀態種類。
3、將所有與某個狀態有關的行爲放到一個類中,並且可以方便地增加新的狀態,只需要改變對象狀態即可改變對象的行爲。
4、允許狀態轉換邏輯與狀態對象合成一體,而不是某一個巨大的條件語句塊。
5、可以讓多個環境對象共享一個狀態對象,從而減少系統中對象的個數。
缺點
1、狀態模式的使用必然會增加系統類和對象的個數。
2、狀態模式的結構與實現都較爲複雜,如果使用不當將導致程序結構和代碼的混亂。
3、狀態模式對"開閉原則"的支持並不太好,對於可以切換狀態的狀態模式,增加新的狀態類需要修改那些負責狀態轉換的源代碼,否則無法切換到新增狀態,而且修改某個狀態類的行爲也需修改對應類的源代碼。
使用場景
行爲隨狀態改變而改變的場景。
- 條件、分支語句的代替者。
狀態模式和策略模式的對比
現在我們知道,狀態模式和策略模式的結構是相似的,但它們的意圖不同。讓我們重溫一下它們的主要不同之處:
- 策略模式封裝了一組相關算法,它允許Client在運行時使用可互換的行爲;狀態模式幫助一個類在不同的狀態顯示不同的行爲。
- 狀態模式封裝了對象的狀態,而策略模式封裝算法或策略。因爲狀態是跟對象密切相關的,它不能被重用;而通過從Context中分離出策略或算法,我們可以重用它們。
- 在狀態模式中,每個狀態通過持有Context的引用,來實現狀態轉移;但是每個策略都不持有Context的引用,它們只是被Context使用。
- 策略實現可以作爲參數傳遞給使用它的對象,例如Collections.sort(),它的參數包含一個Comparator策略。另一方面,狀態是Context對象自己的一部分,隨着時間的推移,Context對象從一個狀態轉移到另一個狀態。
- 雖然它們都符合OCP原則,策略模式也符合SRP原則(單一職責原則),因爲每個策略都封裝自己的算法,且不依賴其他策略。一個策略的改變,並不會導致其他策略的變化。
- 另一個理論上的不同:策略模式定義了對象“怎麼做”的部分。例如,排序對象怎麼對數據排序。狀態模式定義了對象“是什麼”和“什麼時候做”的部分。例如,對象處於什麼狀態,什麼時候處在某個特定的狀態。
- 狀態模式中很好的定義了狀態轉移的次序;而策略模式並無此需要:Client可以自由的選擇任何策略。
- 一些常見的策略模式的例子是封裝算法,例如排序算法,加密算法或者壓縮算法。如果你看到你的代碼需要使用不同類型的相關算法,那麼考慮使用策略模式吧。而識別何時使用狀態模式是很簡單的:如果你需要管理狀態和狀態轉移,但不想使用大量嵌套的條件語句,那麼就是它了。
- 最後但最重要的一個不同之處是,策略的改變由Client完成;而狀態的改變,由Context或狀態自己。
項目實例
simple1包中主要是對風扇的開關狀態進行轉換,其實我們是把狀態放在狀態類中進行按照固定的邏輯轉換,但是這種模式其實他不符合開閉原則,爲什麼了,因爲一旦我們發生新增、修改或者刪除狀態的時候,就需要修改狀態類中的狀態轉換。
public class Application {
public static void main(String[] args) {
Context context = new Context(new CloseLevelState());
context.right();
context.right();
context.right();
context.left();
context.right();
context.right();
}
}
抽象狀態
public interface LevelState {
/**
* 左轉
*
* @param context
*/
void left(Context context);
/**
* 右轉
*
* @param context
*/
void right(Context context);
/**
* 當前檔位
* @return
*/
String info();
}
具體檔位狀態,我只列了2個,其他的類似
@Slf4j
public class OneLevelState implements LevelState {
@Override
public void left(Context context) {
LevelState levelState = new CloseLevelState();
context.setLevelState(levelState);
log.info("風扇左轉到{}", levelState.info());
}
@Override
public void right(Context context) {
LevelState levelState = new TwoLevelState();
context.setLevelState(levelState);
log.info("風扇右轉到{}", levelState.info());
}
@Override
public String info() {
return "1檔";
}
}
@Slf4j
public class CloseLevelState implements LevelState {
@Override
public void left(Context context) {
LevelState levelState = new ForeLevelState();
context.setLevelState(levelState);
log.info("風扇左轉到{}", levelState.info());
}
@Override
public void right(Context context) {
LevelState levelState = new OneLevelState();
context.setLevelState(levelState);
log.info("風扇右轉到{}", levelState.info());
}
@Override
public String info() {
return "0檔";
}
}
真正的開關也就是上下文
@Data
@AllArgsConstructor
public class Context {
private LevelState levelState;
public void left() {
levelState.left(this);
}
public void right() {
levelState.right(this);
}
public String info() {
return levelState.info();
}
}
參考
歡迎關注