管理軟件中的常見代碼設計模式,來自於業務上的需要,有不恰當的地方歡迎批評指正。
1 RE-TRY 重試模式
場景:在連接數據庫服務器時,如果SQL Server數據庫沒有啟動或正在啟動,我們需要有一個連接重試的策略。發送郵件通知時,我們也需要在發送失敗后,多次的嘗試發送以保證郵件能到達目的用戶。
代碼參考:
int maxRetry = 30;
int retryInterval = 10000;
for (int i = 1; i <= maxRetry; i++){try
{//connect to the database server
}catch (Exceptions exception)
{if (i < maxRetry)
Thread.Sleep(retryInterval);else
return; //返回或停止重試操作}}
這種模式的主要是有一個重試行為,直到執行完成或是超過了約定的時間或次數則放棄。
2 Before-Perform-After 檢查-執行-傳送模式
場景:在做一項業務操作前,子類為了重寫(override)基類的行為必須先做條件或環境檢查,然后執行相應的業務操作,之后還可以將此次操作的結果繼續傳送到其它業務單元中。
以打印報表為例子,業務窗體需要先檢查當前登錄用戶是否具備打印權限,如無權限則取消本次操作。代碼例子:
protected internal virtual bool DoPerformPrint(){CancelableRecordPrintEventArgs e = new CancelableRecordPrintEventArgs(this.CurrentEntity, selectionForumlas, formulaFields, parameterFields);this.OnBeforePrint(e);
if (this._beforePrint != null)this._beforePrint(this, e);if (e.Cancel)
return false;this.Print(ref selectionForumlas, ref formulaFields, ref parameterFields);EventArgs args = new EventArgs();
this.OnAfterPrint(args);
if (this._afterPrint != null)this._afterPrint(this, args);}
通過這段代碼應該容易理解我說的這種設計模式,第一段是條件檢查,如果我們在子類中傳入參數e.Cancel=true,則
此方法返回,不再執行Print方法。這種設計模式在事件機制中用的比較多。基類為了控制好業務單元整體的行為,同時又不失去靈活性,可以參照這種模式。
3 TRY 嘗試模式
場景:嘗試性的去執行某一個業務單元,並以它的結果來決定下一步的行為。
比如我們加載數據,如果加載失敗了,則下一步要阻止數據綁定,再比如傳送文件,如果雙方連接失敗,則要阻止文件傳送。
代碼例子:
private void LoadData(){try
{FindAndLoadData ()this._isDataLoaded = true;}catch (Exception exception)
{this._isDataLoaded =false;}}
這個模式的關鍵就在於定義的變量_isDataLoaded。在這個方法中,我們嘗試去加載數據,如果有異常,則將此變量設為false表示加載失敗,其它的業務單元檢查到此變量的值以決定下一步操作。
這種行為也有危害,它隱藏了真實的異常原因,常常用在一些界面操作中,頻繁的拋出各種異常會讓用戶反感,這時可以參考這種模式,嘗試操作失敗后,根據狀態值阻止錯誤進一步發生。
4 BackgroundWorker 多線程工作
場景:在一些業務計算或是讀取數據等耗費時間的業務操作,比如物需求計算,工作單批次發套料,計划訂單發放等業務中,耗費的時間相當多,這種模式可改善效率。
應用代碼例子:
List<LoadMasterScheduleWorker> workers = new List<LoadMasterScheduleWorker>();
for (int i = 0; i < MAX_RUNNING_THREAD; i++){LoadMasterScheduleWorker worker = new LoadMasterScheduleWorker(this, mrp, mpsRows, loadedMpsItems);workers.Add(worker);}WorkerThreadBase.StartAndWaitAll(workers.ToArray());MAX_RUNNING_THREAD通常取當前系統的CPU個數(MAX_RUNNING_THREAD),在實際工作的線程中,采用下面的方法執行數據分配並啟動相應的業務操作:
while (_mpsRows.Count > 0)
{DataRow mpsRow = null;
if (!_mpsRows.TryTake(out mpsRow))break;
LoadMasterSchedule(_mrp, mpsRow);}
從Dictionary<DataRow>集合中不停的TryTake數據行(DataRow)來操作,直到取完所有的數據為止。
需要注意前一段方法的最后一句StartAndWaitAll方法,這里要等待當前所有子線程執行完成,避免數據沒有操作完就進行后面的業務操作。
WorkerThreadBase的源代碼參考這里。
http://stackoverflow.com/questions/597590/c-sharp-threading-patterns-is-this-a-good-idea
http://files.cnblogs.com/files/JamesLi2015/WorkerThreadBase.zip
5 Data Class 數據類
場景:減少代碼中不必要的字符書寫錯誤,改善程序可維護性。
舊代碼是這樣的:
DataTable query = new FastSerializableDataTable("BomRoutingTable");query.Columns.Add("BomNo", typeof(string));query.Columns.Add("SeqNo", typeof(decimal));query.Columns.Add("OpCode", typeof(string));
應用數據類之后,代碼是這樣的:
DataTable query = new FastSerializableDataTable("BomRoutingTable");query.Columns.Add(BomFields.BomNo, typeof(string));query.Columns.Add(BomFields.SeqNo, typeof(decimal));query.Columns.Add(BomFields.OpCode, typeof(string));class BomFields
{public const string BomNo ="BomNo";public const string SeqNo ="SeqNo";public const string OpCode ="OpCode";}
新代碼設計方式增加了一個類型定義,增加了一點復雜性,同時改善可維護性。創建DataTable的列來自於一個類型定義,如果有多個創建表的地方,則可顯著改善書寫效率。
6 History 歷史記錄
場景:ERP中標准物料清單需要通過ECN來變更,若是將此模式應用到其它業務,則做采購單修改或銷售單修改等其它單據,也需要記錄變更前的資料。
例子代碼:
BomHistoryEntity bomHistory = new BomHistoryEntity();
InitialBomHistoryHeaderInActivation(bomHistory, ecn, canUpdate);return bomHistory;
變更記錄相當於流水帳,記錄每次業務修改前的值。變更記錄還可以通過數據審計功能來實現,不過審計功能過於抽象,只能記錄到表的字段變化,要實現兩行數據記錄字段值的精確比較,還需要開發接口程序以適應特定的業務單據。
歷史記錄模式最大的殺手就是反審核,反過帳,若業務單據可以反復修改,審核之后退回,反審核又可以修改,這樣反復的操作,會產生大量的歷史數據。
7 Hash Verify 哈希值驗證
場景:文件傳送時,在文件傳送完成后,需要驗證文件傳送前后的MD5值或SHA1是否一致以保證文件沒有丟失字節。
在一些重要的業務場景,比如轉帳,充值,系統登錄,在業務操作完成后我們還需要驗證一下業務發生是否合理。
代碼例子:
session = Login(userId, passwordHash, companyCode, languageCode, hostEntry.HostName, domainUserName, currentProcess.SessionId)string clientHash = Shared.GetMD5HashValue(string.Concat(new object[] {AssemblyVersion.Company, AssemblyVersion.Product, AssemblyVersion.Version, AssemblyVersion.FileVersion, AssemblyVersion.Product, session.UserId, session.UserGroup, session.CompanyCode, session.SessionId, session.LanguageCode, session.HostName, session.DomainUserName}));string privateKey = "...";string serverHash = RSACryptionHelper.DecryptString(session.ClientHashValue, privateKey);
if (string.CompareOrdinal(clientHash, serverHash) != 0)throw new AppException("Unable to login, unexpected error detected");
第一行代碼登錄成功之后,服務器返回一個session會話對象,此對象包含一個ClientHashValue的MD5值,與此同時我們將客戶端的登入信息,再次構造成一個字符串進行MD5處理,以比較兩者的值是否一致,若不一致可能是非法登錄請求,拋出異常,阻止登入系統。
這個方法也可應用於多版本策略中,系統要阻止3.2版本的客戶端登錄到2.1版本的服務器端,注意到上面的代碼中有Version參數,會拋出異常。這樣可保證一台電腦安裝多個版本的服務器端而不相互沖突。