設計模式之狀態模式(三分鐘學會一個設計模式)

王若伊_恩赐解脱發表於2024-09-12

狀態模式(State Pattern)的定義是這樣的:類的行為是基於它的狀態改變的。
注意這裡的狀態不是狹義的指物件維護了一個“狀態”欄位,我們傳入了不同的列舉值,物件整體的表現行為(對外方法)就改變了。
而是指內部的(任意)欄位如果發生了變化,那麼它的狀態就變了,那麼它對外的表現形式就變了。
它是物件導向的23種設計模式中的一種,屬於行為模式的範圍。
通常我們在解決不同狀態下,對外方法的不同表現時,可以定義若干的列舉,然後寫一大堆if、 elseif、 switch等選擇命令來區分不同的狀態,然後走不同的業務分支。
而狀態模式是支援將這些分支業務抽離出一個獨立類(狀態類),我們透過傳入不同的狀態類,就可以動態的執行不同的業務方法。
整體的結構大概是這樣的:

業務類維護了一個內部狀態物件,這個狀態物件支援由外部傳入,切換為不同的狀態物件。
而這些狀態物件都統一實現了具體的方法,業務類內部在執行業務方法時,會呼叫這些狀態物件中實現的方法。(防盜連線:本文首發自http://www.cnblogs.com/jilodream/ )這樣在切換狀態時,業務方法就會呼叫不同的狀態物件的方法了。從物件導向的角度,實現了狀態變化,類行為的同步變化。
來看一個具體的程式碼示例:

列舉類

1 package com.example.demo.learn.pattern.behavior.status;
2 
3 public enum TextStatusEnum {
4     ONLY_READ,
5     READ_WRITE,
6     UNAVAILABLE;
7 
8 }

狀態定義介面

 1 package com.example.demo.learn.pattern.behavior.status;
 2 
 3 /**
 4  * @discription
 5  */
 6 public interface TextState {
 7      TextStatusEnum getStatus();
 8 
 9      void write(String content);
10 
11      void clear();
12 
13      String read();
14 
15      void setContent(StringBuilder sb);
16 }

只讀狀態

 1 package com.example.demo.learn.pattern.behavior.status;
 2 
 3 import lombok.Data;
 4 import lombok.extern.slf4j.Slf4j;
 5 
 6 /**
 7  * @discription
 8  */
 9 @Slf4j
10 @Data
11 public class OnlyReadState implements TextState {
12     private static final TextStatusEnum textStatus = TextStatusEnum.ONLY_READ;
13 
14     private StringBuilder sb;
15 
16     @Override
17     public TextStatusEnum getStatus() {
18         return textStatus;
19     }
20 
21     public void write(String content) {
22         log.error("sorry, you can not write");
23     }
24 
25     public void clear() {
26         log.error("sorry, you can not clear");
27     }
28 
29     public String read() {
30         return sb.toString();
31     }
32 
33     @Override
34     public void setContent(StringBuilder sb) {
35         this.sb = sb;
36     }
37 }

讀寫狀態

 1 package com.example.demo.learn.pattern.behavior.status;
 2 
 3 import lombok.Data;
 4 import lombok.extern.slf4j.Slf4j;
 5 
 6 /**
 7  * @discription
 8  */
 9 @Data
10 @Slf4j
11 public class ReadWriteState implements TextState {
12     private static final TextStatusEnum textStatus = TextStatusEnum.ONLY_READ;
13 
14     private StringBuilder sb = new StringBuilder();
15 
16     @Override
17     public TextStatusEnum getStatus() {
18         return textStatus;
19     }
20 
21     public void write(String content) {
22         sb.append(content);
23     }
24 
25     public void clear() {
26         sb.setLength(0);
27     }
28 
29     public String read() {
30         return sb.toString();
31     }
32 
33     @Override
34     public void setContent(StringBuilder sb) {
35         this.sb = sb;
36     }
37 }

本文編輯器(業務類/上下文)

 1 package com.example.demo.learn.pattern.behavior.status;
 2 
 3 import lombok.Data;
 4 import lombok.extern.slf4j.Slf4j;
 5 
 6 /**
 7  * @discription
 8  */
 9 @Slf4j
10 public class TextEditor {
11 
12     private StringBuilder sb = new StringBuilder();
13 
14     private TextState textState;
15 
16     public void setState(TextState textState) {
17         textState.setContent(sb);
18         this.textState = textState;
19     }
20 
21     public void write(String content) {
22         if (textState == null) {
23             log.error("no state exist");
24             return;
25         }
26         textState.write(content);
27     }
28 
29     public void clear() {
30         if (textState == null) {
31             log.error("no state exist");
32             return;
33         }
34         textState.clear();
35     }
36 
37     public String read() {
38         if (textState == null) {
39             log.error("no state exist");
40             return "no state";
41         }
42         return textState.read();
43     }
44 
45 }

主類

 1 package com.example.demo.learn.pattern.behavior.status;
 2 
 3 import lombok.extern.slf4j.Slf4j;
 4 
 5 /**
 6  * @discription
 7  */
 8 @Slf4j
 9 public class PatternMain {
10     public static void main(String[] args) {
11         TextEditor editor = new TextEditor();
12         String text;
13 
14         //可讀寫狀態
15         TextState rw = new ReadWriteState();
16         editor.setState(rw);
17         for (int i = 0; i < 3; i++) {
18             editor.write("write" + i);
19             text = editor.read();
20             log.warn("read :" + text);
21         }
22         editor.clear();
23         text = editor.read();
24         log.warn("after clear, we read :" + text);
25         editor.write("last write");
26 
27         log.warn("-----------------------now, we exchange state to only read-----------------------" );
28         //只讀狀態
29         TextState or = new OnlyReadState();
30         editor.setState(or);
31         for (int i = 0; i < 3; i++) {
32             editor.write("write" + i);
33             text = editor.read();
34             log.warn("read :" + text);
35         }
36         editor.clear();
37         text = editor.read();
38         log.warn("after clear, we read :" + text);
39     }
40 }

輸出效果如下:

10:02:52.356 [main] WARN com.example.demo.learn.pattern.behavior.status.PatternMain - read :write0
10:02:52.368 [main] WARN com.example.demo.learn.pattern.behavior.status.PatternMain - read :write0write1
10:02:52.369 [main] WARN com.example.demo.learn.pattern.behavior.status.PatternMain - read :write0write1write2
10:02:52.371 [main] WARN com.example.demo.learn.pattern.behavior.status.PatternMain - after clear, we read :(防盜連線:本文首發自http://www.cnblogs.com/jilodream/ )
10:02:52.372 [main] WARN com.example.demo.learn.pattern.behavior.status.PatternMain - -----------------------now, we exchange state to only read-----------------------
10:02:52.376 [main] ERROR com.example.demo.learn.pattern.behavior.status.OnlyReadState - sorry, you can not write
10:02:52.378 [main] WARN com.example.demo.learn.pattern.behavior.status.PatternMain - read :last write
10:02:52.378 [main] ERROR com.example.demo.learn.pattern.behavior.status.OnlyReadState - sorry, you can not write
10:02:52.378 [main] WARN com.example.demo.learn.pattern.behavior.status.PatternMain - read :last write
10:02:52.379 [main] ERROR com.example.demo.learn.pattern.behavior.status.OnlyReadState - sorry, you can not write
10:02:52.379 [main] WARN com.example.demo.learn.pattern.behavior.status.PatternMain - read :last write
10:02:52.379 [main] ERROR com.example.demo.learn.pattern.behavior.status.OnlyReadState - sorry, you can not clear
10:02:52.380 [main] WARN com.example.demo.learn.pattern.behavior.status.PatternMain - after clear, we read :last write

Process finished with exit code 0

我們可以看到在最初設定讀寫狀態後,可以做讀、寫、清除等操作

在設定讀狀態後則只能讀了。

這樣回頭來看,其實我們就是將不同if Switch的選擇分支,連同選擇的狀態,一同封裝到不同的狀態類中,我們需要新增一種分支邏輯,不再需要修改選擇分支,而是隻需要新增一個狀態類即可。
那是否狀態模式可以替代傳統的if 選擇分支,答案是不能,本質上還是一個度的原因,面相物件如果過度設計,會導致類的數量無限膨脹,難以維護,試想如果存在多個狀態欄位(status、type等),則實體物件的狀態是由多個狀態欄位組合而成的,每增加一個新的狀態欄位,都會導致狀態的數量快速增加,這顯然不是我們想看到的。

相關文章