含义:就一个类而言,应该仅有一个引起它变化的原因。
字面解释:对于一个类而言,所应当拥有的职责应该只有一个。
KISS(keep it stupid and simple)原则要求尽量简单,假如一个类负责了多个任务,则是违背了这个原则。
单一职责原则从理解上讲非常容易懂,但是实际应用中,会遇到很多阻碍。比如,我们单个类的职责简单了,但对于实现一个具体功能,往往会需要做很多事情,如果一个类只能负责一个职责的话,那么可能会需要非常多的类。同时,职责的拆分也会带来分析的复杂度。
日常生活的例子
比如,日常做饭的例子,我们需要一个厨师。如果简单就这样一个需求的话,那么仅仅一个厨师就够了。但是,在复杂一点的场景下,厨师要做很多菜,他就没有时间去买菜,他需要有人,给他准备材料。这个时候做饭这件事情,又衍生出了准备材料这个职责。而这个职责原本是厨师就可以干的事情。有做饭的材料可能还不够,为了能够加快效率,厨师可能还需要人将这些材料加工好,比如,洗菜,切菜等等。
正如上面所描述的那样,一个做饭的需求,就包含了买材料,加工材料,烹饪这几个工作。如果几个工作都比较简单,则可以直接划分到厨师的工作范围内,我们给封装一个方法叫做cook()就完成了,而里面的几个工作,其实就是拆分的小步骤而已。
而当其中的任何一个步骤都变得比较复杂的情况下,那么,我们的拆分就变得有必要了。
比如,买材料要涉及到去超市,选择食材,结账等等事情,加工材料包括清洗,切片等等。
而烹饪操作则又包括了(以做酸菜鱼为例)
- 将鱼肉切片放入盆中, 加入蛋清、料酒、淀粉、盐,搅拌均匀。 腌制20min。
- 将锅烧热之后倒入少许油,盖过锅底。放入姜片、蒜泥、郫县豆瓣酱,炒至可以闻到香味。
- 放入切碎的酸菜丝翻炒均匀,倒入足量的开水,水量需要没过锅内的材料以及即将放入的鱼片等,待水沸腾之后放入金针菇段。
- 最后将鱼片一片片加入锅中并且用筷子拨散,一直煮到所有鱼片变色之后即可出锅。
原本只需要一个厨师,后面开始需要买菜员买菜,加工人员洗菜切菜。
以上就是我们现实生活中对于职责这个事情由简单到复杂的演变过程。
往往在业务之初,我们常常能够满足单一职责的需求。但是在开发过程中由于业务变得复杂,以至于原有的类不再符合单一职责原则。这就是程序变得复杂、代码出现坏味道的缘由。
我们要做的工作便是,在合适的时候将类进行拆分,具体在什么时候拆分,则需要大家在业务中进行平衡。
代码开发的例子
需求的变动
我们需要开发一个接口,功能是接收用户前台的一个修改用户信息的请求。
对于这个需求,可能只需要一件事情,就是更改数据库
那么我们一般情况下,会通过一个接口类接入请求,封装DTO交给业务类BusinessService,在业务类中调用DAO将数据更新到数据库中
我们围绕业务类展开:
第一次需求变动,我们需要对入参进行校验,比如用户id必填校验
此时,变动比较简单,我们直接在BusinessService中加个if-else逻辑判断,不符合条件的,直接返回即可。
第二次需求变动,我们需要对用户的手机号码进行校验,手机号码的格式校验。
此时,变动还是比较简单,我们直接在BusinessService中加个if-else逻辑判断,不符合条件的,直接返回即可。
第n次需求变动,我们需要对用户的地址进行校验,用户地址不能为空校验。
此时,已经有了n个if-else的代码块,这个时候,我们单纯通过数if-else的个数就已经可以确认,该类已经开始变得复杂了。但是主体业务逻辑其实没有变。
“就一个类而言,应该仅有一个引起它变化的原因”
简单的拆分
这个原则,此时其实已经被打破。我们迫切地需要将校验的逻辑拆分出去。
这个时候,我们可以通过建一个校验类Validator的方式,将所有对于参数校验的逻辑都放到该类中的校验方法validate()中,此时,对于BusinessService类而言,他其实就是做了两件事,第一件事通过Validator 校验参数,通过DAO将数据更新到数据库中。
这种是最简单的业务拆分方案。
复杂的业务
后面,需求很可能会还有变化。
用户级别限制,对改id的需求,还需要看是否已经达到当年修改id的上限。不同级别的用户修改id的上限是不同的。
改名卡规则:修改id的时候,还要看是否有改名卡,如果有的话,则要消耗一张改名卡,
计费逻辑:需要消耗虚拟金币的情况下,还要调用外围的计费系统进行虚拟扣费。。。巴拉巴拉,一大堆的东西。
在业务上都是有可能的。因此简单的一个业务需求,按照传统写法,来一个需求,就加一段逻辑,最后该类中,会出现奇奇怪怪的代码片段,维护的时候,会变得异常复杂。
适时合理地拆分
我们要做的是,在合适的时机将业务剥离出来。
判断会员级别并确定是否可以修改的逻辑,需要交给会员类Member,通过调用Member类来判断是否可改。
扣费的逻辑则交给计费类Charging。
那么以后如果是在参数校验上的逻辑改变、会员级别调整逻辑上的改变、数据库层字段上面的改变,都不会影响到BusinessService类,仅有一个引起它变化的原只有,大的业务逻辑上又要加上或者去掉某个功能。
代码主干的逻辑应该是
class BusinessService{
public void doBusiness(){
Validator.validate();
Member.doMemberBusiness();
Charging.charge();
SomeOther.otherBusiness();
DAO.save();
}
}
持续治理,持续重构
实际开发中可能情形更加复杂,我们需要就事论事进行处理,同时要深深的牢记这个原则,记住,代码是慢慢产生坏味道的。持续治理,持续重构,则是良药