管理軟體中的常見程式碼設計模式,來自於業務上的需要,有不恰當的地方歡迎批評指正。
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引數,會丟擲異常。這樣可保證一臺電腦安裝多個版本的伺服器端而不相互衝突。