JAVA設計模式什麼鬼(狀態)——作者:凸凹裡歐
狀態State,指某事物所處的狀況或形態,比如水的三態,零下會變成固態冰,常溫會是液態水,100℃會蒸發成氣態的水蒸氣。
在這個地球生態系統中,水的總量並不會增加,也不會減少,只是隨著溫度的變化其分子間發生了稀鬆緊密的變化罷了,於是便有了不同的行為,比如流動、凝固、或是蒸騰,但對於其本質H2O分子物件並沒有任何變化,變化的,只是其形態。
當然,事物的狀態都是不同的,有的多有的少。物質基本三態,人的精神狀態更是非常複雜多變的,喜怒哀樂,五味雜陳。更有趣的是,對於某些患有嚴重的精神分裂的病人來說,其精神狀態更是變化無常,有些竟可以扮演幾十種角色,隨時間或境遇切換,一會變成精明聰穎的律師,一會是懦弱的失敗者總是要自殺,一個境遇觸發又是憤怒的殺人暴徒,這人格切換速度,喪心病狂到令人髮指。
言歸正傳,依舊老慣例,我們還是用極簡主義陰陽二態來做藥引子,想必每個人家裡都有開關吧,其暴露出兩個UI可操作介面(對接你的手指):開,關。很簡單吧?
好我們來分析一下,首先得定義一個類吧,就叫它:Switcher好了,對外暴露兩個方法:switchOn()以及switchOff(),以便使用者呼叫,OK,開始我們的程式碼。
1 public class Switcher { 2 //false代表關,true代表開 3 private boolean state = false;//初始狀態是關 4 5 public void switchOn(){ 6 state = !state; 7 System.out.println("OK...燈亮"); 8 } 9 10 public void switchOff(){ 11 state = !state; 12 System.out.println("OK...燈滅"); 13 } 14 }
完成了?沒問題了?這也太簡單了吧?當然說這個沒問題是在前端UI殼子設計精妙的前提下,但這並不能代表我們的程式設計沒問題。試想如果UI可以重複呼叫開或者關會出現什麼情況?狀態亂套了!這個設計是非常不可靠的,我們不能因為表面設計上的完美就忽略了後端程式碼功能的邏輯正確性,表裡不一。這就是為什麼我們做應用時不但要做好前端校驗(使用者體驗),更要保證後端校驗(功能正確性)不可缺失。
想明白了的話我們繼續,現在改一下我們之前的設計,這裡一定要加入針對當前狀態的條件判斷,也就是說,開的狀態不能再開,關的狀態不能再關!
1 public class Switcher { 2 //false代表關,true代表開 3 boolean state = false;//初始狀態是關 4 5 public void switchOn(){ 6 if(state == false){//當前是關狀態 7 state = true; 8 System.out.println("OK...燈亮"); 9 }else{//當前是開狀態 10 System.out.println("WARN!!!通電狀態無需再開"); 11 } 12 } 13 14 public void switchOff(){ 15 if(state == true){//當前是開狀態 16 state = false; 17 System.out.println("OK...燈滅"); 18 }else{//當前是關狀態 19 System.out.println("WARN!!!斷電狀態無需再關"); 20 } 21 } 22 }
我們可以看到這裡加入了邏輯判斷,如果重複開或者重複關的話是會告警的,當然這裡也可以拋異常出去,我們就不搞那麼複雜化了。那對於這樣的設計沒有問題吧?很顯然,邏輯上是跑的通的,寫個Client類測試一下。
public class Client {
public static void main(String[] args) {
Switcher s = new Switcher();
s.switchOff();//WARN!!!斷電狀態無需再關
s.switchOn();//OK...燈亮
s.switchOff();//OK...燈滅
s.switchOn();//OK...燈亮
s.switchOn();//WARN!!!通電狀態無需再開
}
}
So far,不管熊孩子怎麼開開關關都不會有問題了。可惜我還是要很遺憾地告訴你,這樣的設計仍然是糟糕的。試想,如果狀態不止一種,並且狀態切換有及其複雜的邏輯,例如,之前那個精神病患者,或者汽車的自動擋。
如果按照這種設計的結果會是?碼農一定要有一種打破砂鍋問到底的精神,不撞南牆不回頭,Lu起袖子馬上幹!我們寫一小段程式碼來看看先。
1 public class Car {
2 //0:Park駐車檔,1:Reverse倒退擋,
3 //2:Neutral空擋,3:Drive前進檔。
4 String state = "P";//初始狀態是P檔
5
6 public void push(){//向上推檔杆
7 switch (state) {
8 case "P"://駐車檔狀態
9 System.out.println("WARN!!!到頭了推不動了!");
10 break;
11 case "R"://倒擋狀態
12 state = "P";
13 System.out.println("OK...切P檔");
14 break;
15 case "N"://空檔狀態
16 System.out.println("OK...切R檔");
17 break;
18 case "D"://前進檔狀態
19 System.out.println("OK...切N檔");
20 break;
21 default:
22 break;
23 }
24 }
25
26 public void pull(){//向下拉檔杆
27 //這裡省略,邏輯同上類似
28 }
29 }
不用多說什麼了吧,這個是在作死了,那一大堆邏輯判斷寫在宿主類裡會越來越像蜘蛛網!我們必須想方設法把這個設計給模組化,把狀態模組給獨立出來!還記得我們曾經講過的設計模式是什麼鬼(策略)吧,演算法策略被抽離出來,這裡舉一反三,把狀態也給抽離出來,好了辦法有了,我們忘掉自動擋,繼續用我們大道至簡的開關例子。
public interface State {
public void switchOn(Switcher switcher);//開
public void switchOff(Switcher switcher);//關
}
以上我們首先了定義一個狀態State介面,兩個方法開與關,注意這裡與策略模式不同的是,我們為了與宿主Switcher對接所以把它作為引數傳入。然後是開狀態與關狀態的實現。
1 public class On implements State {
2 @Override
3 public void switchOn(Switcher switcher) {
4 System.out.println("WARN!!!通電狀態無需再開");
5 return;
6 }
7
8 @Override
9 public void switchOff(Switcher switcher) {
10 switcher.setState(new Off());
11 System.out.println("OK...燈滅");
12 }
13 }
1 public class Off implements State {
2 @Override
3 public void switchOn(Switcher switcher) {
4 switcher.setState(new On());
5 System.out.println("OK...燈亮");
6 }
7
8 @Override
9 public void switchOff(Switcher switcher) {
10 System.out.println("WARN!!!斷電狀態無需再關");
11 return;
12 }
13 }
顯而易見,注意看第10行程式碼,開狀態不能做開行為,只告警並返回,關狀態反之亦然。而第4行程式碼則是合法的行為,所以可以進行狀態切換並實施相應行為,也就是說,開狀態可關,關狀態可開。注意這裡是把宿主物件傳入進來用於切換其當前狀態,亦或是呼叫宿主的具體功能方法(這裡省略用列印輸出代替),比如宿主裡的一盞燈提供的方法。
至此,一切看起來非常優雅,我們已經成功的將狀態從宿主中抽離了,最後再來看宿主開關類是什麼樣子。
1 public class Switcher {
2 //開關的初始狀態設定為“關”
3 private State state = new Off();
4
5 public State getState() {
6 return state;
7 }
8
9 public void setState(State state) {
10 this.state = state;
11 }
12
13 public void switchOn(){
14 state.switchOn(this);//這裡呼叫的是當前狀態的開方法
15 }
16
17 public void switchOff(){
18 state.switchOff(this);//這裡呼叫的是當前狀態的關方法
19 }
20 }
甚至我們還可以給裡面加一盞燈,像之前我們提到的那樣,在State狀態介面實現裡去呼叫。
public class Switcher {
//...之上程式碼略...
private Lamp lamp;
public void lampOn(){
lamp.on();
}
public void lampOff(){
lamp.off();
}
}
看明白了吧?是不是很像策略模式?其實它就是策略的一個變種,只不過狀態模式會更好的根據當前的狀態去實施不同的行為,並且自主切換到另一個正確的狀態,開變關,關變開。就好似電梯(雖然是嵌入式面向過程,這裡只是舉例),使用者根本無法隨意強制更改其狀態以及行為,你讓它上,它不一定馬上就能上,否則會造成事故。電梯內部封裝了多個狀態以及對應的邏輯產生不同的行為,它會根據當前狀態去自我調整並實施最優方案,以達到安全、高效的目的,這才是可靠的設計。
這些例子都很簡單吧?確實很簡單,但也不簡單,例子本身簡單,理解並不簡單,所以大家一定要多分析思考,舉一反三,最終才能融匯貫通,自由運用。光說不練是不行的,理論指導實踐,實踐加強理論,建議大家親自去寫一下上面的汽車自動擋例子。