六大设计原则之单一职责原则(Single Responsibility Principle)

定义

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

通过创建了两个分别专门处理薪水和晋升的部门,会计部门和人事部门的类:FinancialApartmentHRApartment,把两个任务(责任)分离了出去,让本该处理这些职责的类来处理这些职责。

这样一来,不仅仅在此次新需求中满足了单一职责原则,以后如果还要增加人事部门和会计部门处理的任务,就可以直接在这两个类里面添加即可。

下面来看一下这两个设计的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:方法里面传入黑色色值。 而且今后可能还会有更多的导航栏样式,那么我们只需要分别改变传入的色值即可,不需要有大量的重复代码了,修改起来也很方便。

如何实践

对于上面的员工类的例子,或许是因为我们先入为主,知道一个公司的合理组织架构,觉得这么设计理所当然。但是在实际开发中,我们很容易会将不同的责任揉在一起,这点还是需要开发者注意的。

你可能感兴趣的:(六大设计原则之单一职责原则(Single Responsibility Principle))