六大设计原则之单一职责原则(Single Responsibil
定义
A class should have a single responsibility, where a responsibility is nothing but a reason to change.
即:一个类只允许有一个职责,即只有一个导致该类变更的原因。
定义的解读
-
类职责的变化往往就是导致类变化的原因:也就是说如果一个类具有多种职责,就会有多种导致这个类变化的原因,从而导致这个类的维护变得困难。
-
往往在软件开发中随着需求的不断增加,可能会给原来的类添加一些本来不属于它的一些职责,从而违反了单一职责原则。如果我们发现当前类的职责不仅仅有一个,就应该将本来不属于该类真正的职责分离出去。
-
不仅仅是类,函数(方法)也要遵循单一职责原则,即:一个函数(方法)只做一件事情。如果发现一个函数(方法)里面有不同的任务,则需要将不同的任务以另一个函数(方法)的形式分离出去。
优点
如果类与方法的职责划分得很清晰,不但可以提高代码的可读性,更实际性地更降低了程序出错的风险,因为清晰的代码会让bug无处藏身,也有利于bug的追踪,也就是降低了程序的维护成本。
代码讲解
单一职责原则的demo比较简单,通过对象(属性)的设计上讲解已经足够,不需要具体的客户端调用。我们先看一下需求点:
需求点
初始需求:需要创造一个员工类,这个类有员工的一些基本信息。
新需求:增加两个方法:
- 判定员工在今年是否升职
- 计算员工的薪水
先来看一下不好的设计:
不好的设计
//================== Employee.h ==================
@interface Employee : NSObject
//============ 初始需求 ============
@property (nonatomic, copy) NSString *name; //员工姓名
@property (nonatomic, copy) NSString *address; //员工住址
@property (nonatomic, copy) NSString *employeeID; //员工ID
//============ 新需求 ============
//计算薪水
- (double)calculateSalary;
//今年是否晋升
- (BOOL)willGetPromotionThisYear;
@end
由上面的代码可以看出:
- 在初始需求下,我们创建了
Employee
这个员工类,并声明了3个员工信息的属性:员工姓名,地址,员工ID。 - 在新需求下,两个方法直接加到了员工类里面。
新需求的做法看似没有问题,因为都是和员工有关的,但却违反了单一职责原则:因为这两个方法并不是员工本身的职责。
-
calculateSalary
这个方法的职责是属于会计部门的:薪水的计算是会计部门负责。 -
willPromotionThisYear
这个方法的职责是属于人事部门的:考核与晋升机制是人事部门负责。
而上面的设计将本来不属于员工自己的职责强加进了员工类里面,而这个类的设计初衷(原始职责)就是单纯地保留员工的一些信息而已。因此这么做就是给这个类引入了新的职责,故此设计违反了单一职责原则。
我们可以简单想象一下这么做的后果是什么:如果员工的晋升机制变了,或者税收政策等影响员工工资的因素变了,我们还需要修改当前这个类。
那么怎么做才能不违反单一职责原则呢?- 我们需要将这两个方法(责任)分离出去,让本应该处理这类任务的类来处理。
较好的设计
我们保留员工类的基本信息:
//================== Employee.h ==================
@interface Employee : NSObject
//初始需求
@property (nonatomic, copy) NSString *name;
@property (nonatomic, copy) NSString *address;
@property (nonatomic, copy) NSString *employeeID;
接着创建新的会计部门类:
//================== FinancialApartment.h ==================
#import "Employee.h"
//会计部门类
@interface FinancialApartment : NSObject
//计算薪水
- (double)calculateSalary:(Employee *)employee;
@end
和人事部门类:
//================== HRApartment.h ==================
#import "Employee.h"
//人事部门类
@interface HRApartment : NSObject
//今年是否晋升
- (BOOL)willGetPromotionThisYear:(Employee*)employee;
@end
通过创建了两个分别专门处理薪水和晋升的部门,会计部门和人事部门的类:FinancialApartment
和 HRApartment
,把两个任务(责任)分离了出去,让本该处理这些职责的类来处理这些职责。
这样一来,不仅仅在此次新需求中满足了单一职责原则,以后如果还要增加人事部门和会计部门处理的任务,就可以直接在这两个类里面添加即可。
下面来看一下这两个设计的UML 类图,可以更形象地看出两种设计上的区别:
UML 类图对比
未实践单一职责原则:
实践了单一职责原则:
可以看到,在实践了单一职责原则的 UML 类图中,不属于
Employee
的两个职责被分类了FinancialApartment
类 和HRApartment
类。(在 UML 类图中,虚线箭头表示依赖关系,常用在方法参数等,由依赖方指向被依赖方)
上面说过除了类要遵循单一职责设计原则之外,在函数(方法)的设计上也要遵循单一职责的设计原则。因函数(方法)的单一职责原则理解起来比较容易,故在这里就不提供Demo和UML 类图了。
可以简单举一个例子:
APP的默认导航栏的样式是这样的:
- 白色底
- 黑色标题
- 底部有阴影
那么创建默认导航栏的伪代码可能是这样子的:
//默认样式的导航栏
- (void)createDefaultNavigationBarWithTitle:(NSString *)title{
//create white color background view
//create black color title
//create shadow bottom
}
现在我们可以用这个方法统一创建默认的导航栏了。 但是过不久又有新的需求来了,有的页面的导航栏需要做成透明的,因此需要一个透明样式的导航栏:
- 透明底
- 白色标题
- 底部无阴影
针对这个需求,我们可以新增一个方法:
//透明样式的导航栏
- (void)createTransParentNavigationBarWithTitle:(NSString *)title{
//create transparent color background view
//create white color title
}
看出问题来了么?在这两个方法里面,创造background view和 title color title的方法的差别仅仅是颜色不同而已,而其他部分的代码是重复的。 因此我们应该将这两个方法抽出来:
//根据传入的颜色参数设置导航栏的背景色
- (void)createBackgroundViewWithColor:(UIColor)color;
//根据传入的标题字符串和颜色参数设置标题
- (void)createTitlewWithColorWithTitle:(NSString *)title color:(UIColor)color;
而且上面的制造阴影的部分也可以作为方法抽出来:
- (void)createShadowBottom;
这样一来,原来的两个方法可以写成:
//默认样式的导航栏
- (void)createDefaultNavigationBarWithTitle:(NSString *)title{
//设置白色背景
[self createBackgroundViewWithColor:[UIColor whiteColor]];
//设置黑色标题
[self createTitlewWithColorWithTitle:title color:[UIColor blackColor]];
//设置底部阴影
[self createShadowBottom];
}
//透明样式的导航栏
- (void)createTransParentNavigationBarWithTitle:(NSString *)title{
//设置透明背景
[self createBackgroundViewWithColor:[UIColor clearColor]];
//设置白色标题
[self createTitlewWithColorWithTitle:title color:[UIColor whiteColor]];
}
而且我们也可以将里面的方法拿出来在外面调用也可以:
设置默认样式的导航栏:
//设置白色背景
[navigationBar createBackgroundViewWithColor:[UIColor whiteColor]];
//设置黑色标题
[navigationBar createTitlewWithColorWithTitle:title color:[UIColor blackColor]];
//设置阴影
[navigationBar createShadowBottom];
设置透明样式的导航栏:
//设置透明色背景
[navigationBar createBackgroundViewWithColor:[UIColor clearColor]];
//设置白色标题
[navigationBar createTitlewWithColorWithTitle:title color:[UIColor whiteColor]];
这样一来,无论写在一个大方法里面调用或是分别在外面调用,都能很清楚地看到导航栏的每个元素是如何生成的,因为每个职责都分配到了一个单独的方法里面。而且还有一个好处是,透明导航栏如果遇到浅色背景的话,使用白色字体不如使用黑色字体好,所以遇到这种情况我们可以在createTitlewWithColorWithTitle:color:
方法里面传入黑色色值。 而且今后可能还会有更多的导航栏样式,那么我们只需要分别改变传入的色值即可,不需要有大量的重复代码了,修改起来也很方便。
如何实践
对于上面的员工类的例子,或许是因为我们先入为主,知道一个公司的合理组织架构,觉得这么设计理所当然。但是在实际开发中,我们很容易会将不同的责任揉在一起,这点还是需要开发者注意的。