表單驗證邏輯--使用Visitor pattern(訪客者)

摘要:表單驗證邏輯-使用訪問者模式(訪客者)

我們專案在執行過程中有了一個需求是在做流程中的驗證,這種驗證必須是要檢查上一站是否完成,而上一站中的資料來源有點像是是會簽加上跨部門簽核,所以驗證作業會有同階與跨階層的關係,相信大家一定也會遇到雷同的問題,這類的問題要判斷的機制特別多,感覺判斷邏輯好像雷同卻又有所不同,我這裡的解法是採用設計模式中的訪問者模式,下面是我寫出來跟大家分享一下,希望能夠幫助到各位。
 
訪問者模式(訪客者)顧名思義就是以訪問的方式來達到你想要的處理(驗證)邏輯,訪問者模式主要有幾個比較值得注意的物件:
  1. 訪客者:最主要的處理者,主要用來控制所有的流程
  2. 問卷:該物件用來定義要訪問的問卷(定義要驗證的問題)
  3. 受訪者:用來定義要被訪問的對象。
  4. 受訪者集中營:用來集中所有需要被訪問的對象
舉個例子:有一張請款單必須要經過簽核才能夠匯款,而每一站簽核的時候必須要先判斷前面的人員是否簽核完成,而如果當前一站遇到會簽,則要判斷前一站是否全部簽核完畢,並且每張請款單必須要加簽附件,而有某幾個的表單(請假單,出差申請單)簽核的邏輯都相同,像這樣的狀況就可以採用訪問者模式(訪客者)來解決驗證流程的部分,依據上述的描述判斷的邏輯可能有下列幾項:
  1. 無會簽時,前一站是否簽核完成。
  2. 有會簽時,前一站的所有人是否簽核都已經完成。
  3. 前一站附件是否都有簽核完成。
  4. 本站的附件是否也都有簽核完成。
目前已經知道有這幾項驗證邏輯,所以我們可以設計一個驗證者(訪客者)來執行所有的驗證,並且我們必須要針對每個驗證邏輯設計一份問卷,所以會有四份問卷,再來有幾種表單便要設計幾個受訪者,所以如果全部表單都要設計的話就會有三個受訪者(請款單,請假單,出差申請單) ,最後再將所有受訪者集中起來管理(受訪者集中營)這樣就開始設計訪問者模式了。
 
最下面是訪問者模式的類別圖與循序圖,依據循序圖來看你會發現一個很好玩的現象,它的作業是呼叫受訪者再反呼叫給計畫書,最後由計畫書再呼叫回去受訪者,它的作業模是基本上如下:
  1. 先由驗證者將所有受訪驗證者全部加入到VerifableCollective中,這個VerifableCollective有點像老板的意味,他收集了所有的員工。
  2. 驗證者會要求VerifableCollective裡面所有的受訪者接受驗證。
  3. VerifableCollective (要求所有受驗證者接受驗證)   → 受驗證者。
  4. 受驗證者 (好我接受驗證,可以開始依據計劃書開始驗證) → 驗證計劃書。
  5. 驗證計劃書 (依據計畫開始驗證受驗證者)        → 受驗證者
實作的方法如下:
 
問卷:設計問卷的用意主要是用來清楚的定義出要驗證邏輯的介面(Interface),並且可以將每個驗證的邏輯獨立起來管理。

    /// <summary>用來設定要驗證的計畫</summary>
    public interface IVerificationPlan {
 
        /// <summary>執行驗證計畫</summary>
        /// <param name="paramVerifable">傳入受驗證的受訪者</param>
        /// <returns>將回傳驗證的結果</returns>
        bool Verify(AVerifable paramVerifable);
    }
 

    /// <summary>用來處理沒有會簽的驗證計畫</summary>
    public class VerifyNoCountersignPlan : IVerificationPlan {
 
        #region 執行驗證
 
        /// <summary>執行驗證</summary>
        /// <param name="paramVerifable">傳入受驗證的受訪者</param>
        /// <returns>將回傳驗證結果</returns>
        public bool Verify(AVerifable paramVerifable) {
            return paramVerifable.VerifyCrossSource();
        }
 
        #endregion

    }

    /// <summary>用來處理有會簽的驗證計畫</summary>
    public class VerifyCountersignPlan : IVerificationPlan {
 
        #region 執行驗證
 
        /// <summary>執行驗證</summary>
        /// <param name="paramVerifable">傳入受驗證的受訪者</param>
        /// <returns>將回傳驗證結果</returns>
        public bool Verify(AVerifable paramVerifable) {
            return paramVerifable.VerifyCrossSource();
        }
 
        #endregion

    }

    /// <summary>用來處理驗證本站附件是否簽核完畢的驗證計畫</summary>
    public class VerifyAnnexStationPlan : IVerificationPlan {
 
        #region 執行驗證
 
        /// <summary>執行驗證</summary>
        /// <param name="paramVerifable">傳入受驗證的受訪者</param>
        /// <returns>將回傳驗證結果</returns>
        public bool Verify(AVerifable paramVerifable) {
            return paramVerifable.VerifyCrossSource();
        }
 
        #endregion

    }
 

    /// <summary>用來處理驗證前一站附件是否簽核完畢的驗證計畫</summary>
    public class VerifyFrontAnnexStationPlan : IVerificationPlan {
 
        #region 執行驗證
 
        /// <summary>執行驗證</summary>
        /// <param name="paramVerifable">傳入受驗證的受訪者</param>
        /// <returns>將回傳驗證結果</returns>
        public bool Verify(AVerifable paramVerifable) {
            return paramVerifable.VerifyCrossSource();
        }
 
        #endregion

    }

受驗證者:受訪者基本上就是客製化的物件了,針對每種不同的驗證種類需求,去定義出要驗證的資料與邏輯,並且執行驗證。


    public abstract class AVerifable {
 
        /// <summary>用來要求所有接受驗證者必須要接受傳入的計畫單</summary>
        /// <param name="paramPlan">傳入要驗證的計畫表</param>
        /// <returns>將回傳驗證結果</returns>
        public virtual bool Accept(IVerificationPlan paramPlan) {
            return paramPlan.Verify(this);
        }
 
 
        /// <summary>開始驗證有上一站附件簽核狀態的資訊</summary>
        /// <returns>將傳回驗證的結果</returns>
        public abstract bool VerifyFrontAnnexStationPlan();
 
        /// <summary>開始驗證有本站附件簽核狀態的資訊</summary>
        /// <returns>將傳回驗證的結果</returns>
        public abstract bool VerifyAnnexStationPlan();
 
        /// <summary>開始驗證沒有會簽的資訊</summary>
        /// <returns>將傳回驗證的結果</returns>
        public abstract bool VerifyNoCountersignPlan ();
 
        /// <summary>開始驗證有會簽的資訊</summary>
        /// <returns>將傳回驗證的結果</returns>
        public abstract bool VerifyCountersignPlan ();
 
    }
 
    /// <summary>請款單</summary>
    public class VerifableBillable : AVerifable {
       
        #region 驗證
 
        /// <summary>開始驗證有上一站附件簽核狀態的資訊</summary>
        /// <returns>將傳回驗證的結果</returns>
        public override bool VerifyFrontAnnexStationPlan() {
            //驗證邏輯
            //... ...
            //... ...
 
            Console.WindowLeft("上一站附件簽核狀態--驗證成功");
            return true;
        }
 
        /// <summary>開始驗證有本站附件簽核狀態的資訊</summary>
        /// <returns>將傳回驗證的結果</returns>
        public override bool VerifyAnnexStationPlan() {
            //驗證邏輯
            //... ...
            //... ...
 
            Console.WindowLeft("本站附件簽核狀態--驗證成功");
            return true;
        }
 
        /// <summary>開始驗證沒有會簽的資訊</summary>
        /// <returns>將傳回驗證的結果</returns>
        public override bool VerifyNoCountersignPlan () {
            //驗證邏輯
            //... ...
            //... ...
 
            Console.WindowLeft("驗證沒有會簽--驗證成功");
            return true;
        }
 
        /// <summary>開始驗證有會簽的資訊</summary>
        /// <returns>將傳回驗證的結果</returns>
        public override bool VerifyCountersignPlan () {
            //驗證邏輯
            //... ...
            //... ...
 
            Console.WindowLeft("驗證有會簽--驗證成功");
            return true;
        }
 
        #endregion
 
    }
 
    /// <summary>請假單</summary>
    public class VerifableLeave : AVerifable {
       
        #region 驗證
 
        /// <summary>開始驗證有上一站附件簽核狀態的資訊</summary>
        /// <returns>將傳回驗證的結果</returns>
        public override bool VerifyFrontAnnexStationPlan() {
            //驗證邏輯
            //... ...
            //... ...
 
            Console.WindowLeft("上一站附件簽核狀態--驗證成功");
            return true;
        }
 
        /// <summary>開始驗證有本站附件簽核狀態的資訊</summary>
        /// <returns>將傳回驗證的結果</returns>
        public override bool VerifyAnnexStationPlan() {
            //驗證邏輯
            //... ...
            //... ...
 
            Console.WindowLeft("本站附件簽核狀態--驗證成功");
            return true;
        }
 
        /// <summary>開始驗證沒有會簽的資訊</summary>
        /// <returns>將傳回驗證的結果</returns>
        public override bool VerifyNoCountersignPlan () {
            //驗證邏輯
            //... ...
            //... ...
 
            Console.WindowLeft("驗證沒有會簽--驗證成功");
            return true;
        }
 
        /// <summary>開始驗證有會簽的資訊</summary>
        /// <returns>將傳回驗證的結果</returns>
        public override bool VerifyCountersignPlan () {
            //驗證邏輯
            //... ...
            //... ...
 
            Console.WindowLeft("驗證有會簽--驗證成功");
            return true;
        }
 
        #endregion
 
    }
 
 
驗證者集中營:該類別主要是將所有的受驗證者集中起來管理,並且透由統一的介面來執行驗證,當然驗證的介面不只可以定義一個,可針對不同的驗證機制設定不同的集中營,這是該後期大家將 Visitor pattern(訪客者)修改的最大差異點。

    /// <summary>受訪者集中營</summary>
    public class VerifableCollective {
 
        /// <summary>用來儲存受訪者</summary>
        private List<AVerifable> VerifableList = new List<AVerifable>();
 
        /// <summary>加入集合</summary>
        /// <param name="paramVerifable">傳入要加入集合的受訪者</param>
        public void Attach(AVerifable paramVerifable) {
            if (VerifableList.Contains(paramVerifable) == false)
                VerifableList.Add(paramVerifable);
        }
 
        /// <summary>移除集合</summary>
        /// <param name="paramVerifable">傳入要從集合物件中刪除的受訪者</param>
        public void Detach(AVerifable paramVerifable) {
            if (VerifableList.Contains(paramVerifable) == true)
                VerifableList.Remove(paramVerifable);
        }
 
        /// <summary>要求所有受訪者接受訪問</summary>
        /// <param name="paramPlan">傳入要訪問的問卷</param>
        /// <returns>將回傳反問完後的結果</returns>
        public bool Accept(IVerificationPlan paramPlan) {
            bool ReturnValue = true;
 
            //要求所有的受訪者接受訪問
            foreach (AVerifable Item in this.VerifableList) {
                //必須要所有的訪問結果都為True
                ReturnValue = (Item.Accept(paramPlan) == true && ReturnValue == true);
            }
 
            //回傳訪問結果
            return ReturnValue;
        }
 
    }
驗證者:該物件是整個Pattern最主要的核心,用來建立起計畫與執行者(受訪者)的關係。

    /// <summary>本類別用來建立驗證者的行為</summary>
    public class Verifier {
 
        private VerifableCollective Verifable = new VerifableCollective();
 
        public void Verify() {

            /// <summary>用來處理沒有會簽的驗證計畫</summary>
            IVerificationPlan Plan1 = new VerifyNoCountersignPlan();
 
            /// <summary>用來處理有會簽的驗證計畫</summary>
            IVerificationPlan Plan2 = new VerifyCountersignPlan();
 
            /// <summary>用來處理驗證本站附件是否簽核完畢的驗證計畫</summary>
            IVerificationPlan Plan3 = new VerifyAnnexStationPlan();

            /// <summary>用來處理驗證前一站附件是否簽核完畢的驗證計畫</summary>
            IVerificationPlan Plan4 = new VerifyFrontAnnexStationPlan();
 
            //加入請假單檢驗邏輯
            Verifable.Attach(new VerifableLeave());
            //加入請款單檢驗邏輯
            Verifable.Attach(new VerifableBillable ());
 
            //要求接受驗證
            Verifable.Accept(Plan1);
            Verifable.Accept(Plan2);
            Verifable.Accept(Plan3);
            Verifable.Accept(Plan4);
        }
    }
 

 

執行結果:
(請假單)
----------------------------
上一站附件簽核狀態--驗證成功
本站附件簽核狀態--驗證成功
驗證沒有會簽--驗證成功
驗證有會簽--驗證成功
 
(請款單)
----------------------------
上一站附件簽核狀態--驗證成功
本站附件簽核狀態--驗證成功
驗證沒有會簽--驗證成功
驗證有會簽--驗證成功
 
 
總結:使用Visitor pattern 最大的好處是在於能夠將「問卷(驗證計畫)」與「受訪者(驗證資料)」兩者分開,並且重複利用「問卷(驗證計畫)」的機制,以及可彈性的設計問卷計畫的內容(哪些要驗證,哪些不用驗證),完成 Reuse 那些看似無法 Reuse 又好像可以的機制,並且如果日後有需要新增或刪除問卷(驗證)項目可以不需要修改以前的程式而輕易的增加/刪除問卷(驗證)項目。
 
 
圖二、循序圖