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