[c#]Module的生成

[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弄進來 ...