Design Pattern - State

(史帝芬, 2007/06/11, hi.steven@gmail.com)
  • 前言

  •     在許多程式裡,常可以看到為了判斷流程的狀態,使用一個很大的switch或一連串的if else, 那樣子寫程式也並非不可以,只是當流程很大,或流程隨需求改變而改變時,程式常會越改越亂, 在物件導向的程式設計裡,為這個情況提供的解決方案就是State Pattern。State Pattern透過將 每一個條件分支抽取成獨立的類別,以便將物件狀態也視為另一個獨立的物件,可獨立改變,如此, 在需求變更時,可減少程式的改變。
        這裡將以一個請假流程為例,其流程如圖1所示,提出申請後由HR備查, HR會先檢查該員工的假是否還有剩,有的話才准假,如果假用完了,就退回給原申請者。 程式1是以switch直覺的寫成,可以發現,程式中以switch判斷在不同狀態下,應該做的事,當需求 改變,越來越大、越來越複雜時,可預見的,switch將會越來越大,其中的邏輯也會越來越複雜。 程式2則是以State Pattern改寫程式1,所有的條件判斷被獨立成一個個的類別,主程式LeaveFlow變 得很簡單,當然啦~這樣的寫法類別變得很多正是它的缺點。

    圖1
  • 程式1 (C#)

  • using System;
    
    namespace State
    {
        public class LeaveFlow
        {
            private const int START_STATE = 0;
            private const int APPLY_STATE = 1;
            private const int HR_STATE = 2;
            private const int STOP_STATE = 3;
            private int current_state;
            private int totalCount = 7;
    
            public LeaveFlow()
            {
                current_state = START_STATE;
            }
    
            public void nextState()
            {
                switch (current_state)
                {
                    case START_STATE:
                        current_state = APPLY_STATE;
                        Console.WriteLine("提出申請");
                        break;
                    case APPLY_STATE:
                        current_state = HR_STATE;
                        Console.WriteLine("核准假單");
                        break;
                    case HR_STATE:
                        if (totalCount == 0)
                        {
                            current_state = APPLY_STATE;
                            Console.WriteLine("回申請流程");
                        }
                        else
                        {
                            totalCount--;
                            current_state = STOP_STATE;
                            Console.WriteLine("結束流程");
                        }
    
                        break;
                    case STOP_STATE:
                        Console.WriteLine("已結束流程");
                        break;
                }
            }
        }
    
        class Program
        {
            static void Main(string[] args)
            {
                LeaveFlow leave = new LeaveFlow();
                leave.nextState();
                leave.nextState();
                leave.nextState();
    
                Console.ReadLine();
            }
        }
    }
    
  • 程式2 (C#)

  • using System;
    
    namespace StatePattern
    {
        public class LeaveFlow
        {
            private State state;
            private int totalCount = 7;
    
            public int TotalCount
            {
                get { return totalCount; }
                set { totalCount = value; }
            }
    
            public LeaveFlow()
            {
                state = new StartState();
            }
    
            public void nextState()
            {
                state.nextState(this);
            }
    
            public void setState(State state)
            {
                this.state = state;
            }
        }
    
        public abstract class State
        {
            abstract public void nextState(LeaveFlow flow);
        }
    
        public class StartState : State
        {
    
            public override void nextState(LeaveFlow flow)
            {
                flow.setState(new ApplyState());
                Console.WriteLine("提出申請");
            }
        }
    
        public class ApplyState : State
        {
            public override void nextState(LeaveFlow flow)
            {
                flow.setState(new HRState());
                Console.WriteLine("核准假單");
            }
        }
    
        public class HRState : State
        {
            public override void nextState(LeaveFlow flow)
            {
                if (flow.TotalCount == 0)
                {
                    flow.setState(new ApplyState());
                    Console.WriteLine("回申請流程");
                }
                else
                {
                    flow.TotalCount = flow.TotalCount - 1;
                    flow.setState(new StopState());
                    Console.WriteLine("結束流程");
                }
            }
        }
    
        public class StopState : State
        {
            public override void nextState(LeaveFlow flow)
            {
                Console.WriteLine("流程已結束");
            }
        }
    
        class Program
        {
            static void Main(string[] args)
            {
                LeaveFlow leave = new LeaveFlow();
                leave.nextState();
                leave.nextState();
                leave.nextState();
    
                Console.ReadLine();
            }
        }
    }
    
  • 說明


  •     GoF書中的class diagram如圖所示,程式2使用state pattern避免了 一堆的if else或龐大的switch,許多人在看到HRState class中仍有if時,會認為這程式應該 有問題? 程式2使用state pattern省去了狀態轉移時的條件判斷,這裡的if判斷式判斷的是剩餘可 請的天數。