
第2章 六大设计原则
2.1 单一职责原则
2.1.1 单一职责原则定义
单一职责原则(Single Responsibility Principle,SRP)又称单一功能原则,是面向对象的五个基本原则(SOLID)之一。它规定一个类应该只有一个发生变化的原因,该原则由罗伯特·C.马丁(Robert C.Martin)在《敏捷软件开发:原则、模式与实践》一书中提出。
如果需要开发的一个功能需求不是一次性的,且随着业务发展的不断变化而变化,那么当一个Class类负责超过两个及以上的职责时,就在需求的不断迭代、实现类持续扩张的情况下,就会出现难以维护、不好扩展、测试难度大和上线风险高等问题。
所谓的职责就是指类变化的原因,也就是业务需求。如果一个类有多于一个的原因被改变,那么这个类就有超过两个及以上的职责。而单一职责约定一个类应该有且仅有一个改变类的原因。
2.1.2 模拟场景
这里通过一个视频网站用户分类的例子,来帮助大家理解单一职责原则的构建方法。当在各类视频网站看电影、电视剧时,网站针对不同的用户类型,会在用户观看时给出不同的服务反馈,如以下三种。
·访客用户,一般只可以观看480P视频,并时刻提醒用户注册会员能观看高清视频。这表示视频业务发展需要拉客,以获取更多的新注册用户。
·普通会员,可以观看720P超清视频,但不能屏蔽视频中出现的广告。这表示视频业务发展需要盈利。
·VIP 会员(属于付费用户),既可以观看 1080P 蓝光视频,又可以关闭或跳过广告。
2.1.3 违背原则方案
下面根据需求场景直接编码,实现一个最简单的基本功能,即根据不同的用户类型,判断用户可以观看视频的类型。

如上,实现业务功能逻辑的方式非常简单,暂时也不会出什么问题。但是这一个类里包含着多个不同的行为,也就是多种用户职责。如果在这样的类上继续扩展功能或添加逻辑,就会显得非常臃肿。
接下来通过单元测试,再看这个类功能的使用。

因为上面的实现方式是在一个类中用if…else判断逻辑,所以在调用方法时是所有的职责用户都使用一个方法实现,作为程序调用入口。对于简单的或者几乎不需要迭代的功能,这种实现也未偿不可。但如果面对频繁迭代的业务需求,这样的代码结构就很难支撑系统迭代,每一次需求的实现都可能会影响其他逻辑,给整个接口服务带来不可控的风险。
2.1.4 单一职责原则改善代码
视频播放是视频网站的核心功能,当核心功能开发完成后,就需要不断地完善用户权限。这样才能更好地运营一家视频网站。
在模拟用户场景中,其实就是在不断地建设用户权益。针对不同的用户类型提供差异化服务,既满足获客需求,又可以让部分用户选择付费。
为了满足这样不断迭代的需求,就不能使用一个类把所有职责行为混为一谈,而是需要提供一个上层的接口类,对不同的差异化用户给出单独的实现类,拆分各自的职责边界。
1.定义接口

定义出上层接口 IVideoUserService,统一定义需要实现的功能,包括:视频清晰级别接口definition()、广告播放方式接口advertisement()。三种不同类型的用户就可以分别实现自己的服务类,做到职责统一。
(1)实现类,访客用户。这个类实现的是访客用户在视频网站中的形态,比如这类用户只能观看480P高清视频,同时需要观看广告。

(2)实现类,普通会员。这个类实现的是普通会员在视频网站中的形态,也就是注册用户可以观看720P超清视频,另外也需要观看广告。

(3)实现类,VIP会员。这个类实现的是VIP会员在视频网站中的形态。因为这类用户已经是付费用户,所以可以观看更高清的视频,同时不需要观看广告。

综上,每种用户对应的服务职责都有对应的类实现,不会互相干扰。当某一类用户需要添加新的运营规则时,操作起来也可以非常方便。比如,所有的注册用户可以发弹幕、付费用户可以点播等。类关系图如图2-1所示。
2.单元测试
接下来再看现在的类服务的使用方式。



图2-1
通过利用单一职责原则的代码优化后,现在每个类都只负责自己的用户行为。后续无论扩展新的功能还是需要在某个时刻修改某个用户行为类,都可以非常方便地开发和维护。
在项目开发的过程中,尽可能保证接口的定义、类的实现以及方法开发保持单一职责,对项目后期的迭代和维护会有很大的帮助。