[c#]Module的生成
Glossary;
1. I/O : 本文是指Input/Output, 而非System.IO.
2. delegate 委派: 主物件專心於核心邏輯, 將某些I/O確定的邏輯區塊, 交由其它物件處理; 在C#可以用具名/匿名的類別/方法來實作.
3. generic 泛型: 建立物件者需指定其回傳之物件型別, 且必然為其物件型態. 如泛型集合List<string>即強制I/O皆為string型別.
4. lambda expression: 匿名函式, 呈現如: Invoke(( ) => "*") , 亦等同委派方法於 Invoke( delegate() { return "*"; }) .
第一章: 需求產生Module
假設我們要為一台Socket Server做些UI的介面, 並以Web 實作.
Socket Server 提供了幾個服務
名稱 | 指令 | 參數 | 輸出 | 範例 |
1. Login | login | id|password | 1.ok 2.user not exist 3.invalid password 4.unknow error | I: ‘login sysadm|passw0rd ‘ O: ‘ok’ |
2. LoginUserCount | login_user_count | n/a | 1. number 2. unknow error | I: ‘login_user_count’ O: ‘10’ |
3. Logout | logout | id | 1. ok 2. unknow error | I: ‘logout sysadm’ O: ‘ok’ |
首先直覺地就會切出一層亦即一個Module, 並給予其名稱,名稱空間: My.Remote
並開始封裝第一個界面
XX Login(string id, string password);
XX LoginUserCount();
XX Logout(string id);
}
思考點: 回傳值(XX)是什麼?
(1) 直接回傳 bool, int , 若有發生問題就 throw Exception(string ErrorMessage);
優點: 直覺.
缺點: 綁死規格. 因沒有明確的錯誤處理, 不同模組引用開發時, 可能會忽略錯誤處理, 而引發Runtime Error.
(2) 封裝一個ResultObject. 儘可能合乎所有回傳狀況及其規則.
優點: 彈性, 不同模組來引用時, 有統一的處理規則.
缺點: 記憶體使用較多.
public bool success; // 成功或失敗
public string message; // 成功的訊息或失敗的訊息
public object data; // 成功時的entity object. 先以弱型別定義
public Exception ex; // 失敗時的error object.
}
ResultObject Login(string id, string password);
ResultObject LoginUserCount();
ResultObject Logout(string id);
}
此時, 再為RemoteSocketClient 做個工廠及Dumy的實作物件, 就可以提交給其它成員引用了.
第二章: 先求有 - 方法抽離
RemoteSocketClient的開發者, 第一個任務是什麼呢? 我的習慣是先開發一個Visual Remote SocketServer, 簡單地用Window Form + Thread + TCPListener實作各種api之
(1) 正常回傳值
(2) 不正常回傳值
(3) 不回傳
等狀況的按鍵, 來模擬伺服器, 並提供client 單元測試的環境. 請自行參考MSDN TCPListener 的Sample code
第二件事來快速撰寫SocketClient的程式碼(最髒版)以及單位測試 亦可參考MSDN TCPClient的Sample code
確認連結/傳值/收值/編碼...等是沒有問題的.
// ... 實作TCPClient
}
public void LoginTest(){
// ... 實作單位測試
}
思考點: 模組內的所有方法都會有實作TCPClient的部份, 可選擇
(1) Copy / Paste
(2) 抽離成一個共用的方法
return WriteAndRead("login " + id + "|" + password);
}
protected string WriteAndRead(string msg) {
// ... 實作TCPClient
}
目前都是直接把結果字串回傳回去, 該是把ResultObject加入了.
string result = WriteAndRead("login " + id + "|" + password);
if("OK".Equals(result)){
return new ResultObject(){ success=true };
}else{
// ... 實作錯誤
}
}
public ResultObject Logout(string id){
string result = WriteAndRead("logout " + id );
if("OK".Equals(result)){
return new ResultObject(){ success=true };
}else{
// ... 實作錯誤
}
}
思考點: 還可以把什麼抽離呢?
第三章: 再求好 - 委派抽離
雖然我們把一些方法抽出了, 若還要更好會有什麼想法呢?
思考點: 其實回傳的內容有些是重覆的, 但有些則否, 通則是相似於自然語言, 這可以再包裝"委派"出去, 由專心在處理語法則來處理吧!!
protected ResultBean Invoke(string command, EndRead handler){
string result = WriteAndRead(command);
return handler(result);
}
我增加了一個EndRead的委派物件, 由呼叫者注入實作者.
{
ResultBean bean = new ResultBean();
if (Const.EtlEngineResponseOK.Equals(response))
{
bean.success = true;
bean.message = Const.MSG_SUCCESS;
bean.data = response;
}
else
{
bean.success = false;
bean.message = response;
bean.data = response;
}
return bean;
}
你可以選擇把方法封裝在父類別或是其它獨立的類別
return Invoke("login " + id + "|" + "password", base.DefaultEndRead)
}
第四章: 還可更好嗎 - 引用泛型
還是有個刺在我心中, 那就是ResultObject.data的弱型別
思考點: 弱型別有彈性, 但實務上卻是臭蟲經常發生的地方.
public bool success; // 成功或失敗
public string message; // 成功的訊息或失敗的訊息
public T data; // 成功時的entity object.
public Exception ex; // 失敗時的error object.
}
很簡單的就可以加入強型別, 但在這個時間做這種Refactoring會被很多人餵木棒,
因為當你一提交時, 除了你改好的code以外, 所有的程式都compile error, 整組人馬都翻過來 XD
結語:要用泛型要及早~模組的重點還是在於封裝出一個共同性的行為,屬性..., 只要權責分得清楚, 加上一點點料, 就可以簡單的抽換了
咦~ 好像沒把lambda弄進來 ...