差异的源头
前言:语言本身是一件非常不稳定的表达工具,这也是为什么我们在沟通中需要观察对方的表情、肢体动作、给予的隐喻、提供的图像来进一步确定对方想表达的意思,加之语言的使用者和接收者因文化、职业、经历等不确定因素的影响,又会造成相同的语句表达出不同含义,这让语言的精确性再次下降。
只有这些?
当我们用搜索引擎搜索 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原则是一个指导性建议,并非强制要求,也切勿生搬硬套。