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