1、前言java
狀態模式在某些場合中使用是很是方便的,什麼叫作狀態,若是你們學過《編譯原理》就會明白DFA M和NFA M,在肯定有限狀態機和非肯定有限狀態機中,狀態就是最小的單元,當知足某種條件的時候,狀態就會發生改變,咱們能夠把時間中的一個時刻當作一個狀態,那麼其實整個社會都是有狀態組成的,前一時刻到下一時刻,整個社會上的物質(空間)發生了什麼樣的變化,所以狀態能夠很是的大也能夠很是的小,天氣變化狀況是狀態,白天和黑夜也是狀態,人的生活做息等等都是狀態,所以狀態無處不在。那麼狀態模式就是將一個狀態看作一個類,這與以往咱們對類的理解不同,以往咱們認爲類是對對象的抽象,用來表示對象的,對象通常是具體的事物,而如今咱們將狀態這種非具體的看不見的但又真實存在的事物當作類描述的東西,這一點可能須要你們理解。設計模式
那麼爲何必需要狀態模式,不用狀態模式能夠嗎?固然能夠,可是仍是回到了代碼的可維護性、可擴展性、可複用性這個層面上來考慮問題,好比咱們本例的內容,考慮一個銀行系統,能夠用來取款、打電話、報警、記錄這四種功能,可是考慮以下需求:在白天若是咱們去取款是正常的,晚上取款就要發出警報;在白天打電話有人接,晚上打電話啓動留言功能;白天和晚上按警鈴都會報警。那麼咱們應該如何設計這個程序呢,固然咱們能夠對每個動做(做爲一個函數),在這個函數內部,咱們進行判斷是白天仍是黑夜,而後根據具體的狀況作出反應。這樣固然是能夠的,可是假如咱們的狀態(白天和黑夜)很是的多呢,好比將24小時分紅24個時間段(24個狀態),那麼咱們對於每個函數就要判斷24遍,這無疑是很是糟糕的代碼,可讀性很是的差,而且若是需求發生了改變,咱們很難去修改代碼(很容易出現錯誤),可是若是咱們考慮將這些狀態都做爲一個類,在每個類內部進行處理、判斷和相應的切換,這樣思路就很是的清晰,若是再增長一種狀態,代碼須要修改的地方會很是的少,對於狀態很是多的情景來講很是的方便。app
2、代碼函數
Context接口:this
package zyr.dp.state; public interface Context { public abstract void setClock(int hour); public abstract void changeState(State state); public abstract void callSecurity(String str); public abstract void recordLog(String msg); }
SafeFrame實現類:spa
package zyr.dp.state; import java.awt.*; import java.awt.Frame; import java.awt.event.ActionEvent; import java.awt.event.ActionListener; public class SafeFrame extends Frame implements Context,ActionListener { private static final long serialVersionUID = 1676660221139225498L; private Button btnUse=new Button("使用"); private Button btnAlarm=new Button("警鈴"); private Button btnPhone=new Button("打電話"); private Button btnExit=new Button("退出"); private TextField tfClock=new TextField(60); private TextArea taAlarm=new TextArea(10,60); private State state=DayState.getInstance(); public SafeFrame(String title){ super(title); setBackground(Color.BLUE); setLayout(new BorderLayout()); add(tfClock,BorderLayout.NORTH); tfClock.setEditable(false); add(taAlarm,BorderLayout.CENTER); taAlarm.setEditable(false); Panel panel=new Panel(); panel.add(btnUse); panel.add(btnAlarm); panel.add(btnPhone); panel.add(btnExit); add(panel,BorderLayout.SOUTH); pack(); show(); btnUse.addActionListener(this); btnAlarm.addActionListener(this); btnPhone.addActionListener(this); btnExit.addActionListener(this); } public void setClock(int hour) { tfClock.setText(hour<10 ? "如今時間是:" + "0"+hour : "如今時間是:" +hour); state.doClock(this, hour); } public void changeState(State state) { System.out.println("從狀態"+this.state+"轉變到了"+state); this.state=state; } public void callSecurity(String str) { taAlarm.append("Call..."+str+"\n"); } public void recordLog(String msg) { taAlarm.append("record..."+msg+"\n"); } public void actionPerformed(ActionEvent e) { if(e.getSource()==btnUse){ state.doUse(this); }else if(e.getSource()==btnAlarm){ state.doAlarm(this); }else if(e.getSource()==btnPhone){ state.doPhone(this); }else if(e.getSource()==btnExit){ System.exit(0); }else{ System.out.print("未預料錯誤!"); } } }
State接口:.net
package zyr.dp.state; public interface State { public abstract void doClock(Context context,int hour); public abstract void doUse(Context context); public abstract void doAlarm(Context context); public abstract void doPhone(Context context); }
NightState實現類:設計
package zyr.dp.state; public class NightState implements State { private NightState(){ } private static NightState nightState=new NightState(); public static NightState getInstance() { return nightState; } public void doClock(Context context, int hour) { if(hour>=6 && hour <18){ //白天 context.changeState(DayState.getInstance()); } } public void doUse(Context context) { context.callSecurity("晚上使用"); } public void doAlarm(Context context) { context.callSecurity("晚上警鈴"); } public void doPhone(Context context) { context.recordLog("晚上打電話"); } }
DayState實現類:3d
package zyr.dp.state; public class DayState implements State { private DayState(){ } private static DayState dayState=new DayState(); public static DayState getInstance() { return dayState; } public void doClock(Context context, int hour) { if(hour<6 || hour >=18){ //晚上 context.changeState(NightState.getInstance()); } } public void doUse(Context context) { context.callSecurity("白天使用"); } public void doAlarm(Context context) { context.callSecurity("白天警鈴"); } public void doPhone(Context context) { context.recordLog("白天打電話"); } }
Main類:代理
package zyr.dp.state; public class Main { public static void main(String[] args) { SafeFrame f=new SafeFrame("狀態模式"); while(true){ for(int hour=1;hour<=24;hour++){ f.setClock(hour); try { Thread.sleep(1000); } catch (InterruptedException e) { e.printStackTrace(); } } } } }
運行結果:
3、總結
能夠看到狀態模式的強大威力,是用最簡潔的代碼經過接口、抽象類、普通類、繼承、委託、代理模式等方式,將狀態抽象爲類,而後經過控制狀態的邏輯委託不一樣的狀態去作不一樣的事情,對於每個狀態來講又再次委託控制狀態的邏輯做出相應的動做和修改,這樣看起來比較複雜,其實仔細閱讀就會發現由於接口的緣由,使得程序很是的簡潔,各個狀態分工明確,密切配合。
可是狀態模式也有一些缺點,正是由於各個狀態密切配合,在一個狀態之中要知道其餘狀態的對象,這就形成了必定的關聯,狀態與狀態之間是一種緊耦合的關係,這是狀態模式的一點缺點,針對於這一點,咱們能夠將狀態遷移的代碼統一交給SafeFrame來作,這樣就要使用到了Mediator仲裁者模式了。
使用單例的緣由是若是一直創造新的對象會對內存產生浪費,由於單例便可。一樣的使用狀態模式經過接口使用state變量來表示相應的狀態,不會產生混淆和矛盾,相比於使用多個變量來分區間表示狀態來講是很是清晰簡練的。State模式便於增長新的狀態(也須要修改其餘狀態的狀態遷移代碼),不便於增長新的「依賴於狀態的處理」,好比doAlarm等,由於一旦增長了,實現了State接口的全部狀態都要增長該部分代碼。
同時咱們也看到了實例的多面性,好比SafeFrame實例實現了ActionListener接口和Context接口,那麼就能夠將new SafeFrame()對象傳入fun1(ActionListener a)和fun2(Context context)這兩個方法之中,以後這兩個方法對該對象的使用是不一樣的,權限也不同,所以多接口就會產生多面性。狀態模式實際上是用了分而治之的思想,將不一樣的狀態分開來討論,抽取共同性,從而使問題變得簡單。