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

如何实践

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

©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 212,222评论 6 493
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 90,455评论 3 385
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 157,720评论 0 348
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 56,568评论 1 284
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 65,696评论 6 386
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 49,879评论 1 290
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 39,028评论 3 409
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 37,773评论 0 268
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 44,220评论 1 303
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 36,550评论 2 327
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 38,697评论 1 341
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 34,360评论 4 332
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 40,002评论 3 315
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 30,782评论 0 21
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,010评论 1 266
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 46,433评论 2 360
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 43,587评论 2 350

推荐阅读更多精彩内容