狀態模式(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等),則實體物件的狀態是由多個狀態欄位組合而成的,每增加一個新的狀態欄位,都會導致狀態的數量快速增加,這顯然不是我們想看到的。