關鍵詞:高內聚低耦合,網絡消息,消息中間件
作者:碼匠信龍
我所理解的高內聚是模塊內部是獨立完成某個單一的功能,盡可能的少而簡單,也就是常說的單一責任原則。低耦合是各個模塊之間相互獨立存在,這樣利於修改和組合。短期來看,並沒有很明顯的好處,甚至短期內會影響系統的開發進度,因為對開發設計人員提出了更高的要求,但長期來看,帶來的好處是使程序更容易維護和修改。
在《由if-else,switch代替方案引起的思考》這篇文章里,有讀者沒太明白這種寫法的好處(高內聚,低耦合), 當時沒有展開來講解。如果業務邏輯不復雜,只有幾種情況時,其實用if-else或者switch更合適。下面通過個簡單的例子來講解:
這次,我們要寫個客戶端收到服務器返回的網絡消息處理函數,換用函數封裝case的邏輯處理代碼,消息處理的偽代碼如下
//代碼示例1
void NetMsgProc( message)
{
switch (message)
{
case MSG_USER_LOGIN: //用戶登錄成功
OnUserLogin();
break;
case MSG_USER_SIGNIN://用戶注冊
OnUserSignIn();
break;
case MSG_USER_PAY_SUCCESS://用戶支付成功
OnUserPaySuccess();
break;
case MSG_USER_PAY_FAIL://用戶支付失敗
OnUserPayFail();
break;
default:
return 0;
}
return 0;
}
看上去,比原來例子要代碼美觀些了, 網絡消息響應的代碼沒有耦合在消息處理函數里,可以分離在實際業務里。

在來看段python偽代碼實現:
//代碼示例2
messages = {}
#綁定消息處理函數和消息ID
messages[MSG_USER_LOGIN] = OnUserLogin()
messages[MSG_USER_SIGNIN] = OnUserSignIn()
messages[MSG_USER_PAY_SUCCESS] = OnUserPaySuccess()
messages[MSG_USER_PAY_FAIL] = OnUserPayFail()
...
def NetMsgProc(self,message):
if message.type in messages.keys():
listener = messages[message.type]
listener(message)
看上去兩段代碼量差不了多少。接下來改寫上面這代碼:
//代碼示例3
messages = {}
#綁定消息處理函數
def NetMsgBind():
messages[MSG_USER_LOGIN] = OnUserLogin()
messages[MSG_USER_SIGNIN] = OnUserSignIn()
messages[MSG_USER_PAY_SUCCESS] = OnUserPaySuccess()
messages[MSG_USER_PAY_FAIL] = OnUserPayFail()
...
def NetMsgProc(self,message):
if message.type in messages.keys():
listener = messages[message.type]
listener(message)
看上去示例代碼3和示例代碼2差不多,只是封裝為函數而已,沒什么區別。注意了,別分神。如果改寫下那個消息初始化函數,把消息ID和對應響應函數抽象出來,作為參數傳入。
//代碼示例4
messages = {}
#消息初始化函數
def NetMsgBind(self,msg_id,callback_fun):
messages[msg_id] = callback_fun
...
def NetMsgProc(self,message):
if message.type in messages.keys():
listener = messages[message.type]
listener(message)
這時候你可以看到,網絡消息處理函數,已經沒有和消息響應函數有耦合了,而是可以分離到具體的業務邏輯代碼里面寫了。

例如用戶支付功能的業務偽代碼如下:
#代碼示例5
#用戶支付業務邏輯
#netManager是封裝了代碼示例4的網絡消息處理類
def UserPay():
netManager.NetSendMsg("MSG_USER_PAY",paydata); //給服務器發送支付消息
def UserPayCallback(): //綁定支付相關的回調處理函數
netManager.NetMsgBind( "MSG_USER_PAY_SUCCESS",OnUserPaySuccess);
netManager.NetMsgBind( "MSG_USER_PAY_FAIL",OnUserPayFail);
def OnUserPaySuccess():
dialog.show("支付成功!")
def OnUserPayFail():
dialog.show("支付失敗!")
這樣,實例代碼4可以作為一個網絡消息處理的核心模塊,跟實際的業務邏輯沒有關系,這個核心模塊甚至可以作為其他業務邏輯的消息處理中間件,例如界面控件響應消息,數據庫請求查詢消息等。如果是用swich-case方式來寫的話,實際的具體業務處理函數會耦合到里面。使用鍵值對映射響應函數后,可以進一步抽象,使得消息和響應函數綁定跟具體業務邏輯分離開來。
這樣這個網絡消息處理核心模塊,小而美,只專注處理兩件事情:
1.綁定消息ID的處理函數
2.服務器消息過來,調用相應消息響應函數
跟具體業務邏輯沒有耦合,這樣具體業務代碼里寫響應函數,代碼維護起來更容易。如果多人協作開發一個項目時,用代碼示例4方式,每個人如果有新增業務邏輯,只要修改他維護的業務邏輯代碼,不需要修改核心模塊,減少項目出錯幾率。
要掌握這種編程思想,寫出短小而美的代碼,我想只有通過不斷的回過頭去反復修改,反復推敲,反復提煉。就像工匠大師那樣,用數十年的精湛技藝,反復打磨地原石,最終以呈現寶石極致美感。

版權聲明:本文為博主原創,歡迎轉載分享,只需注明作者與出處http://thinkingroom.me
