2011年3月31日 星期四

[OO 設計模式] 大話設計模式 : 無盡加班何時休 - 狀態模式 (State)

前言 : 
想想你上班的情況, 上班狀態好, 中午想睡覺, 下午漸漸進入狀況, 如果工作完成便可以準時下班, 否則就只能加班煎熬. 而這其實是一種狀態的變化, 不同的時間會有不同的狀態. 接著我們將用代碼來模擬真實人生, 藉此來介紹有名的狀態模式 (State Design Pattern). 

工作模式 - 函數版 : 
第一版程式簡單的用 if/else 來判斷時間做出不同狀態的反應, 範例代碼如下 : 

- WorkStatus.java :
  1. package chk16.v1;  
  2.   
  3. public class WorkStatus {  
  4.     static int Hour = 0;  
  5.     static boolean WorkFinished = false// 任務完成標記  
  6.       
  7.     public static void WriteProgram(){  
  8.         if(Hour < 12) {  
  9.             System.out.printf("當前時間: {%d} 上午工作, 精神百倍! ^^\n", Hour);  
  10.         }  
  11.         else if(Hour < 13)  
  12.         {  
  13.             System.out.printf("當前時間: {%d} 餓了, 午飯; 覺得睏, 午休... ><\n", Hour);  
  14.         }  
  15.         else if(Hour < 17)  
  16.         {  
  17.             System.out.printf("當前時間: {%d} 下午狀態不錯, 繼續努力! ╰_╯\n", Hour);  
  18.         }  
  19.         else  
  20.         {  
  21.             if(WorkFinished)  
  22.             {  
  23.                 System.out.printf("當前時間: {%d} 下班回家. ^^\n", Hour);  
  24.             }  
  25.             else  
  26.             {  
  27.                 if(Hour < 21)  
  28.                 {  
  29.                     System.out.printf("當前時間: {%d} 加班喔, 好想睡.  //(ㄒoㄒ)// \n", Hour);  
  30.                 }  
  31.                 else  
  32.                 {  
  33.                     System.out.printf("當前時間: {%d} 不行了, 快睡著了zzz  x__x \n", Hour);  
  34.                 }  
  35.             }  
  36.         }  
  37.     }  
  38.       
  39.     public static void main(String args[]) {  
  40.         Hour = 9;  
  41.         WriteProgram();  
  42.         Hour = 10;  
  43.         WriteProgram();  
  44.         Hour = 12;  
  45.         WriteProgram();  
  46.         Hour = 13;  
  47.         WriteProgram();  
  48.         Hour = 14;  
  49.         WriteProgram();  
  50.         Hour = 17;  
  51.         WriteProgram();  
  52.           
  53.         WorkFinished = false;  
  54.         Hour = 19;  
  55.         WriteProgram();  
  56.         Hour = 22;  
  57.         WriteProgram();  
  58.     }  
  59. }  

接著觀察執行結果如預期, 但是我們寫的是 OO 耶, 這樣傳統程序導向的程式碼是會被笑掉大牙的, 所以接著我們封裝了時間與工作狀態屬性到 Work 類別... 

工作狀態 - OO 導向版 : 
這裡用 OO 封裝了時間與工作狀態到類別 Work : 

- Work.java : 第二版真實人生
  1. package chk16.v2;  
  2.   
  3. public class Work {  
  4.     private int hour;  
  5.     private boolean isFinished = false;  
  6.       
  7.     public void WriteProgram()  
  8.     {  
  9.         if(hour < 12) {  
  10.             System.out.printf("當前時間: {%d} 上午工作, 精神百倍! ^^\n", hour);  
  11.         }  
  12.         else if(hour < 13)  
  13.         {  
  14.             System.out.printf("當前時間: {%d} 餓了, 午飯; 覺得睏, 午休... ><\n", hour);  
  15.         }  
  16.         else if(hour < 17)  
  17.         {  
  18.             System.out.printf("當前時間: {%d} 下午狀態不錯, 繼續努力! ╰_╯\n", hour);  
  19.         }  
  20.         else  
  21.         {  
  22.             if(isFinished)  
  23.             {  
  24.                 System.out.printf("當前時間: {%d} 下班回家. ^^\n", hour);  
  25.             }  
  26.             else  
  27.             {  
  28.                 if(hour < 21)  
  29.                 {  
  30.                     System.out.printf("當前時間: {%d} 加班喔, 好想睡.  //(ㄒoㄒ)// \n", hour);  
  31.                 }  
  32.                 else  
  33.                 {  
  34.                     System.out.printf("當前時間: {%d} 不行了, 快睡著了zzz  x__x \n", hour);  
  35.                 }  
  36.             }  
  37.         }  
  38.     }  
  39.       
  40.     public int getHour() {  
  41.         return hour;  
  42.     }  
  43.     public void setHour(int hour) {  
  44.         this.hour = hour;  
  45.         WriteProgram();  
  46.     }  
  47.     public boolean isFinished() {  
  48.         return isFinished;  
  49.     }  
  50.     public void setFinished(boolean isFinished) {  
  51.         this.isFinished = isFinished;  
  52.     }  
  53.       
  54.     public static void main(String args[]) {  
  55.         Work emergencyProject = new Work();  
  56.         emergencyProject.setHour(9);   
  57.         emergencyProject.setHour(10);  
  58.         emergencyProject.setHour(12);  
  59.         emergencyProject.setHour(13);  
  60.         emergencyProject.setHour(14);  
  61.         emergencyProject.setHour(17);  
  62.         emergencyProject.setHour(19);  
  63.         emergencyProject.setHour(22);  
  64.     }  
  65. }  

這裡我們觀察到方法 WriteProgram() 仍然存在的太多的 if/else. 考慮 物件導向設計其實是希望做到程式碼的責任分解. 因而 Work 類別違背了 "單一職責原則 (SRP)" 與 "開放-封閉原則 (OCP)", 幸運的是前人(GoF)已經為我們針對這類問題提供了解決方案, 那就是 "狀態模式". 

狀態模式 : 
我們先來看看狀態模式的定義 : 

The State Pattern allows an object to alter its behavior when its internal state changes. The object will appear to change its class.

狀態模式主要解決的是當控制一個物件狀態轉換的條件運算式過於複雜時的情況. 把狀態的判斷邏輯轉移到表示不同狀態的一系列類別當中, 就可以把複雜的判斷邏輯簡化 . 當然如果狀態判斷很簡單, 那就沒必要用 "狀態模式" 了. 底下是其 UML 圖 : 
 

狀態模式的好處是將與特定狀態相關的行為局部化, 並且將不同狀態的行為分割開來. 也就是將所有與狀態相關的程式碼都存在於某個 ConcreteState 中, 所已透過定義新的子類別就可以很容易的增加新的狀態和轉換. 說白了就是為了消除龐大的條件分支敘述. 因此狀態模式透過把各種狀態轉移邏輯到 IState 的 Concrete 類別之間, 來減少相互間的依賴. 

工作狀態 - 採用 State Pattern : 
接著我們來看看採用了 State Pattern 的版本 : 

- IState.java :
  1. package chk16.v4;  
  2.   
  3. public interface IState {  
  4.     public void WriteProgram(Work w);  
  5. }  


- AfternoonState.java :
  1. package chk16.v4.states;  
  2.   
  3. import chk16.v4.IState;  
  4. import chk16.v4.Work;  
  5.   
  6. public class AfternoonState implements IState{  
  7.   
  8.     @Override  
  9.     public void WriteProgram(Work w) {  
  10.         if(w.getHour()<17)  
  11.         {  
  12.             System.out.printf("當前時間: {%d} 下午狀態不錯, 繼續努力! ╰_╯\n", w.getHour());  
  13.         }  
  14.         else  
  15.         {  
  16.             // 超過 17 點, 則轉入傍晚狀態.  
  17.             w.setState(new EveningState()); w.WriteProgram();  
  18.         }                     
  19.     }  
  20. }  


- EveningState.java :
  1. package chk16.v4.states;  
  2.   
  3. import chk16.v4.IState;  
  4. import chk16.v4.Work;  
  5.   
  6. public class EveningState implements IState {  
  7.   
  8.     @Override  
  9.     public void WriteProgram(Work w) {  
  10.         if(w.isFinished())  
  11.         {  
  12.             // 完成任務, 則轉入下班狀態  
  13.             w.setState(new RestState()); w.WriteProgram();  
  14.         }  
  15.         else  
  16.         {  
  17.             if(w.getHour()<21)  
  18.             {  
  19.                 System.out.printf("當前時間: {%d} 加班喔, 好想睡.  //(ㄒoㄒ)// \n", w.getHour());  
  20.             }  
  21.             else  
  22.             {  
  23.                 // 超過 21 點, 則進入睡眠工作狀態.  
  24.                 w.setState(new SleepingState()); w.WriteProgram();  
  25.             }  
  26.         }  
  27.     }  
  28. }  


- MorningState.java :
  1. package chk16.v4.states;  
  2.   
  3. import chk16.v4.IState;  
  4. import chk16.v4.Work;  
  5.   
  6. public class MorningState implements IState{  
  7.     @Override  
  8.     public void WriteProgram(Work w) {  
  9.         if(w.getHour()<12)  
  10.         {  
  11.             System.out.printf("當前時間: {%d} 上午工作, 精神百倍! ^^\n", w.getHour());  
  12.         }  
  13.         else  
  14.         {  
  15.             // 超過 12 點, 則轉入中午工作狀態  
  16.             w.setState(new NoonState()); w.WriteProgram();   
  17.         }         
  18.     }  
  19. }  


- NoonState.java :
  1. package chk16.v4.states;  
  2.   
  3. import chk16.v4.IState;  
  4. import chk16.v4.Work;  
  5.   
  6. public class NoonState implements IState{  
  7.   
  8.     @Override  
  9.     public void WriteProgram(Work w) {  
  10.         if(w.getHour()<13)  
  11.         {  
  12.             System.out.printf("當前時間: {%d} 餓了, 午飯; 覺得睏, 午休... ><\n", w.getHour());  
  13.         }  
  14.         else   
  15.         {  
  16.             // 超過 13 點, 則進入下午工作模式.  
  17.             w.setState(new AfternoonState()); w.WriteProgram();  
  18.         }         
  19.     }  
  20. }  


- RestState.java :
  1. package chk16.v4.states;  
  2.   
  3. import chk16.v4.IState;  
  4. import chk16.v4.Work;  
  5.   
  6. public class RestState implements IState{  
  7.     @Override  
  8.     public void WriteProgram(Work w) {  
  9.         System.out.printf("當前時間: {%d} 下班回家. ^^\n", w.getHour());          
  10.     }  
  11. }  


- SleepingState.java :
  1. package chk16.v4.states;  
  2.   
  3. import chk16.v4.IState;  
  4. import chk16.v4.Work;  
  5.   
  6. public class SleepingState implements IState {  
  7.   
  8.     @Override  
  9.     public void WriteProgram(Work w) {  
  10.         if(w.isFinished())  
  11.         {  
  12.             // 完成工作就下班  
  13.             w.setState(new RestState()); w.WriteProgram();  
  14.         }  
  15.         else  
  16.         {  
  17.             System.out.printf("當前時間: {%d} 不行了, 快睡著了zzz  x__x \n", w.getHour());  
  18.         }  
  19.     }  
  20. }  


- Work.java : State Pattern UML 裡的 Context
  1. package chk16.v4;  
  2.   
  3. import chk16.v4.states.MorningState;  
  4.   
  5. public class Work {  
  6.     private int hour;  
  7.     private boolean isFinished = false;   
  8.     private IState current;  
  9.       
  10.     public Work(){current = new MorningState();}  
  11.       
  12.     public void WriteProgram(){WriteProgram(hour);}  
  13.     public void WriteProgram(int h) {  
  14.         this.hour = h;  
  15.         current.WriteProgram(this);  
  16.     }  
  17.       
  18.     public int getHour() {  
  19.         return hour;  
  20.     }  
  21.     public void setHour(int hour) {  
  22.         this.hour = hour;  
  23.     }  
  24.     public boolean isFinished() {  
  25.         return isFinished;  
  26.     }  
  27.     public void setFinished(boolean isFinished) {  
  28.         this.isFinished = isFinished;  
  29.     }  
  30.   
  31.     public IState getState() {  
  32.         return current;  
  33.     }  
  34.   
  35.     public void setState(IState current) {  
  36.         this.current = current;  
  37.     }  
  38.       
  39.     public static void main(String args[]) {  
  40.         Work emgyProjects = new Work();  
  41.         emgyProjects.WriteProgram(9);  
  42.         emgyProjects.WriteProgram(10);  
  43.         emgyProjects.WriteProgram(12);  
  44.         emgyProjects.WriteProgram(13);  
  45.         emgyProjects.WriteProgram(14);  
  46.         emgyProjects.WriteProgram(17);  
  47.         emgyProjects.WriteProgram(19);  
  48.         emgyProjects.WriteProgram(22);  
  49.     }  
  50. }  

沒有留言:

張貼留言

[Git 常見問題] error: The following untracked working tree files would be overwritten by merge

  Source From  Here 方案1: // x -----删除忽略文件已经对 git 来说不识别的文件 // d -----删除未被添加到 git 的路径中的文件 // f -----强制运行 #   git clean -d -fx 方案2: 今天在服务器上  gi...