单一职责原则-SRP
一、怎样理解单一职责原则
1.概念
SRP :Single responsibility principle 的缩写。一个函数,或者一个模块,应该只负责一类行为。
需要注意的是,每个函数应该只做一件事,并且把这件事做好。这是SRP 在实现底层细节的时候的一个体现。SRP是一种思想,需要用心体验。大到整个软件,小到一个函数,变量。都适用。不要把它当成一种技巧。
2.演化
单一职责最开始的描述:
任何的一个软件,有且仅有一个被修改的理由。
那为什么要修改呢?因为客户需求变化,因为使用者需求变化。所以可以换一个描述:
任何的软件(模块,函数),仅仅对一个使用者需求负责。
虽然是不同客户的需求,但是也可能几个客户对软件的需求修改是一样的。所以再换一个:
一个模块或者函数,只对某一类行为负责。
用自己的话讲,就是看谁用这个模块(函数)。使用者不同,软件职责就可能不同,职责不同就要想办法分开,强行放在一起,就违反SRP。
二、案例
1.案例问题说明
假设有一段需要计算员工工作时长的函数。
float employee_calculate_work_hours(employee_desc *employee_info)
{
//996 工作制
float x = 12*6;
return x;
}
它可能有两个用处:
- 计算工资。
- 用来做考核。
float employee_calculate_salary(employee_desc *employee_info)
{
//计算工资一小时一百块
float salary = 100*employee_calculate_work_hours(employee_info);
return salary;
}
float employee_check(employee_desc *employee_info)
{
//考核分数计算,工作时长*基本能力系数
float check = 2*employee_calculate_work_hours(employee_info);
return check;
}
现在需求变动,加班没有工资。每天按照8小时来计算:
float employee_calculate_work_hours(employee_desc *employee_info)
{
//虽然996小时工作,但是工资只有5*8.
float x = 8*5;
return x;
}
然后测试了一下,工资计算非常合理,就提交了代码。
最后就出现,员工的考核结果也受到了影响。因为考核也按照8*5来计算了。
虽然都是计算工作时长,但是因为给不通过的部门使用,employee_calculate_work_hours 函数的职责在这种情况下其实不一样。
SRP原则要求,这样的代码必须分开。
2.案例问题解决
修改后的代码:
typedef enum work_hours_responsibility
{
E_RESPONSIBILITY_CALCULATE_SALARY,
E_RESPONSIBILITY_WORKER_CHECK
}work_hours_responsibility;
typedef struct employee_desc
{
//可能还有其它的属性,先略过了。
work_hours_responsibility responsibility;
};
typdef int (*calculate_work_hours_callback)(void *param);
typedef struct calculate_work_hours_map_desc
{
work_hours_responsibility responsibility;
calculate_work_hours_callback fn;
}calculate_work_hours_map_desc;
static float employee_cal_work_hours_for_salary(employee_desc *employee_info);
static float employee_cal_work_hours_for_check(employee_desc *employee_info);
//采用数据驱动编程,提升代码可读性。
//因为职责不同,所以分成两个函数
calculate_work_hours_map_desc calculate_work_hours_map[]=
{
{E_RESPONSIBILITY_CALCULATE_SALARY,employee_cal_work_hours_for_salary},
{E_RESPONSIBILITY_WORKER_CHECK,employee_cal_work_hours_for_check}
};
//在不同的场景中,设置不同的责任,则计算的方法也不同。
float employee_cal_work_hours_for_salary(employee_desc *employee_info)
{
float x = 5*8;
return x;
}
float employee_cal_work_hours_for_check(employee_desc *employee_info)
{
float x = 12*6;
return x;
}
//修改后函数接口不变,但是不同场景计算结果已经不同。
float employee_calculate_work_hours(employee_desc *employee_info)
{
int i = 0;
for(i = 0; i < sizeof(calculate_work_hours_map)/sizeof(calculate_work_hours_map[0]); i++)
{
if(calculate_work_hours_map[i].fn != NULL)
{
return calculate_work_hours_map[i].fn(employee_info);
}
}
return 0.0;
}
三、无脑遵守SRP会有什么问题?
问题:
如果一开始就考虑SRP,可能会纠结,函数就不能复用了吗?
就像上述的例子。难道一开始,需求变化之前,计算时间的函数,就要分开写?刚开始写两个一模一样的函数,就是命名不一样?
那什么时候就要分开,什么时候就要复用?
解答:
我理解的是,如果当前你和你的团队想不到当前函数可能被使用的多个场景。就不要着急使用单一职责原则。先让其复用。
如果出现了需要分别处理的情况,则需要对代码进行重构,重构的时候,再使用 SPR原则。
SPR原则,应该是防止你已经知道或者能预料到这个软件会有多个职责,但是依然要把它们挤在一起的这种情况。
四、参考文献
《架构整洁之道》