差異的源頭
前言:語(yǔ)言本身是一件非常不穩(wěn)定的表達(dá)工具,這也是為什么我們?cè)跍贤ㄖ行枰^察對(duì)方的表情、肢體動(dòng)作、給予的隱喻、提供的圖像來(lái)進(jìn)一步確定對(duì)方想表達(dá)的意思,加之語(yǔ)言的使用者和接收者因文化、職業(yè)、經(jīng)歷等不確定因素的影響,又會(huì)造成相同的語(yǔ)句表達(dá)出不同含義,這讓語(yǔ)言的精確性再次下降。
只有這些?
當(dāng)我們用搜索引擎搜索 SRP原則或單一職責(zé)原則關(guān)鍵字,定義中使用頻率最多的一句話就是:一個(gè)類(lèi)應(yīng)該只有一個(gè)發(fā)生變化的原因。
這不僅讓讀者陷入思索,其中所描述的原因到底是什么?是否可量化?
以量取勝
為了進(jìn)一步解釋這個(gè)"原因",我們對(duì)其定義豐富一下:
- 一個(gè)類(lèi)應(yīng)該只有一個(gè)發(fā)生變化的原因。
- 每一個(gè)類(lèi)都應(yīng)該對(duì)程序功能的一個(gè)部分負(fù)責(zé),此時(shí)它應(yīng)該封裝。該模塊、類(lèi)或函數(shù)的所有服務(wù)都應(yīng)該與該職責(zé)緊密結(jié)合。
- 將因相同原因而發(fā)生變化的事物聚集在一起。分開(kāi)由于不同原因而改變的事物。
以上的三條定義說(shuō)的都是一件事:?jiǎn)我宦氊?zé)原則。
這也能看出,這個(gè)發(fā)生變化的"原因"是基于一個(gè)集合。如果每個(gè)函數(shù)只做一件事是一個(gè)機(jī)器的零件,那單一職責(zé)中的"職責(zé)"也就是所說(shuō)的“原因”就是這些零件組合起來(lái)的功能。
確定單一
既然我們已經(jīng)從概念上統(tǒng)一了職責(zé)到底是什么,那么下一步就是從量化的角度確定如何保證職責(zé)單一。
分為如下三步
- 建模
- 編碼對(duì)應(yīng)的類(lèi)(筆者開(kāi)始階段常用偽代碼代替)
- 將對(duì)應(yīng)的類(lèi)拆分為多個(gè)類(lèi)直到不能拆分
建模:可以理解為對(duì)應(yīng)需求的梳理和拆分,最終抽象(總結(jié))出這個(gè)職責(zé)核心是做什么的,要依賴(lài)于哪些其他的職責(zé)。
應(yīng)用
我們可以舉個(gè)例子來(lái)說(shuō)明,我們要做一個(gè)菜單界面功能,一般我們會(huì)這么寫(xiě)
public class MenuPanel
{
public MenuPanel()
{
var menuData = GetMenuData();
SetMenuDataAndUpdate(menuData);
}
public string GetMenuData()
{
// do something...
return default;
}
public void SetMenuDataAndUpdate(string temp)
{
// do something...
}
}
在這個(gè)MenuPanel類(lèi)中負(fù)責(zé)顯示Menu這個(gè)菜單界面,但是這個(gè)MenuPanel真的是僅僅負(fù)責(zé)顯示嗎?答案是否定的。
當(dāng)?shù)玫讲邉澬枨蟮臅r(shí)候,可按照【確定單一】里面所說(shuō)的3步驟進(jìn)行如下操作
建模
- 根據(jù)數(shù)據(jù)庫(kù)的數(shù)據(jù)顯示對(duì)應(yīng)的菜單。
- 數(shù)據(jù)方面需要:請(qǐng)求-驗(yàn)證-解析-整合-篩選等操作。相當(dāng)于上述代碼
GetMenuData()函數(shù)。 - UI方面需要:接收數(shù)據(jù)-數(shù)據(jù)分類(lèi)填充-適配布局-注冊(cè)響應(yīng)事件等操作。相當(dāng)于上述代碼
SetMenuDataAndUpdate()函數(shù)。 - 將上面數(shù)據(jù)與UI進(jìn)行銜接操作。相當(dāng)于上述代碼
MenuPanel()。
拆分對(duì)應(yīng)的類(lèi)直到不能再拆分
- 數(shù)據(jù)處理一個(gè)類(lèi)
- UI顯示一個(gè)類(lèi)
- 數(shù)據(jù)與UI銜接一個(gè)類(lèi)
經(jīng)過(guò)拆分后的代碼
public class ServerData
{
public string GetNeedData()
{
/*
* 請(qǐng)求 函數(shù)
* 驗(yàn)證 函數(shù)
* 解析 函數(shù)
* 整合 函數(shù)
* 篩選 函數(shù)
*/
return default;
}
}
public class MenuPanel
{
private string m_NeedData;
public MenuPanel(string needData)
{
m_NeedData = needData;
}
public void UpdateMenu()
{
// do something...
}
}
public class EnterMenuPanelCommand
{
public void Excute()
{
var serverData = new ServerData();
var needData = serverData.GetNeedData();
//TODO:正常情況下不應(yīng)該直接傳入基本類(lèi)型,應(yīng)該傳入對(duì)應(yīng)的自定義類(lèi),為了示例簡(jiǎn)單暫且代替
var menuPanel = new MenuPanel(needData);
menuPanel.UpdateMenu();
}
}
經(jīng)過(guò)一系列的操作我們得到三個(gè)職責(zé):數(shù)據(jù)處理、UI刷新、數(shù)據(jù)與UI銜接三個(gè)職責(zé)。這樣每一個(gè)類(lèi)當(dāng)職責(zé)變化,即只有一個(gè)發(fā)生變化原因時(shí),我們才需要更改對(duì)應(yīng)的類(lèi)。
總結(jié)
SRP原則并不是徹底消滅腐朽代碼的銀彈,它只是降低出現(xiàn)代碼壞的味道的幾率,提高代碼整潔的系數(shù)。SRP原則是一個(gè)指導(dǎo)性建議,并非強(qiáng)制要求,也切勿生搬硬套。