折騰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();

? ? }

}

歡迎工作一到五年的Java工程師朋友們加入Java程序員開發: 721575865

群內提供免費的Java架構學習資料(里面有高可用、高并發、高性能及分布式、Jvm性能調優、Spring源碼,MyBatis,Netty,Redis,Kafka,Mysql,Zookeeper,Tomcat,Docker,Dubbo,Nginx等多個知識點的架構資料)合理利用自己每一分每一秒的時間來學習提升自己,不要再用"沒有時間“來掩飾自己思想上的懶惰!趁年輕,使勁拼,給未來的自己一個交代!

?著作權歸作者所有,轉載或內容合作請聯系作者
平臺聲明:文章內容(如有圖片或視頻亦包括在內)由作者上傳并發布,文章內容僅代表作者本人觀點,簡書系信息發布平臺,僅提供信息存儲服務。

推薦閱讀更多精彩內容

  • 設計模式分類 總體來說設計模式分為三大類:創建型模式,共五種:工廠方法模式、抽象工廠模式、單例模式、建造者模式、原...
    lifeline丿毅閱讀 1,256評論 0 2
  • 面向對象的六大原則 單一職責原則 所謂職責是指類變化的原因。如果一個類有多于一個的動機被改變,那么這個類就具有多于...
    JxMY閱讀 973評論 1 3
  • 接口約定 使用httpsrestful風格用戶端:https://{域名}/api/{接口版本號}/{resour...
    寶寶愛櫻花閱讀 615評論 0 51
  • 今天是開心為您每天分享一本書的第185天。 今日共讀:《科學是怎樣敗給迷信的》 一、科學敗給迷信的第一個原因——媒...
    五感自律研習社閱讀 1,193評論 0 0
  • 都是你的錯:要求別人為我們的感受負責,逃避我們在人際關系中的責任; 都是我的錯:我們想要為別人的感受負責,承擔我們...
    博採閱讀 224評論 0 0