面向对象基本原则(1)- 单一职责原则与接口隔离原则
面向对象基本原则(1)- 单一职责原则与接口隔离原则
面向对象基本原则(1)- 单一职责原则与接口隔离原则
面向对象基本原则(2)- 里式代换原则与依赖倒置原则
面向对象基本原则(3)- 最少知道原则与开闭原则
一、单一职责原则
1. 单一职责原则简介
单一职责原则的英文名称是 Single Responsibility Principle,简称SRP。
单一职责原则的原话解释是:
There should never be more than one reason for a class to change.
意思是:应该有且仅有一个原因引起类的变更。
2. 单一职责原则优点
- 类的复杂性降低,实现什么职责都有清晰明确的定义。
- 可读性提高,复杂性降低,那当然可读性提高了。
- 可维护性提高,可读性提高,那当然更容易维护了。
- 变更引起的风险降低,变更是必不可少的,如果接口的单一职责做得好,一个接口修改只对相应的实现类有影响,对其他的接口无影响,这对系统的扩展性、维护性都有非常大的帮助。
3. 最佳实践
接口一定要做到单一职责,类的设计尽量做到只有一个原因引起变化。
注意 单一职责原则提出了一个编写程序的标准,用“职责”或“变化原因”来衡量接口或类设计得是否优良,但是“职责”和“变化原因”都是不可度量的,因项目而异,因环境而异。
4. Show me the code
代码使用PHP7.2语法编写
用户业务场景
IUserBo 接口负责用户属性
interface IUserBo { public function setUserID(string $userID); public function getUserID() : string ; public function setPassword(string $password); public function getPassword() : string ; public function setUserName(string $userName); public function getUserName() : string ; }
IUserBiz 接口负责用户行为
interface IUserBiz { public function changePassword(string $password) : bool ; public function deleteUser(IUserBo $userBo) : bool ; public function mapUser(IUserBo $userBo); public function addOrg(IUserBo $userBo, int $orgID) : bool; /** * 给用户添加角色 * @param IUserBo $userBo * @param int $roleID * @return bool */ public function addRole(IUserBo $userBo, int $roleID) : bool ; }
UserInfo 类实现 IUserBo, IUserBiz 两个接口
class UserInfo implements IUserBo, IUserBiz { public function setUserID(string $userID) { // TODO: Implement setUserID() method. } public function getUserID(): string { // TODO: Implement getUserID() method. } public function setPassword(string $password) { // TODO: Implement setPassword() method. } public function getPassword(): string { // TODO: Implement getPassword() method. } public function setUserName(string $userName) { // TODO: Implement setUserName() method. } public function getUserName(): string { // TODO: Implement getUserName() method. } public function changePassword(string $password): bool { // TODO: Implement changePassword() method. } public function deleteUser(IUserBo $userBo): bool { // TODO: Implement deleteUser() method. } public function mapUser(IUserBo $userBo) { // TODO: Implement mapUser() method. } public function addOrg(IUserBo $userBo, int $orgID): bool { // TODO: Implement addOrg() method. } public function addRole(IUserBo $userBo, int $roleID): bool { // TODO: Implement addRole() method. } }
$userInfo = new UserInfo(); // 赋值,就认为它是一个纯粹的属性 $userInfo->setPassword("abc"); // 执行动作,就认为是一个业务逻辑类 $userInfo->deleteUser($userInfo);
二、接口隔离原则
1. 接口隔离原则简介
接口隔离原则的英文名称是 Interface Segregation Principle,简称ISP。
接口隔离原则的英文定义有两种:
Clients should not be forced to depend upon interfaces that they don't use. 客户端不应该被迫依赖于他们不使用的接口。 The dependency of one class to another one should depend on the smallest possible interface. 类间的依赖关系应该建立在尽可能小的接口上。
我们可以把这两个定义概括为一句话:建立单一接口,不要建立臃肿庞大的接口。
再通俗一点讲:接口尽量细化,同时接口中的方法尽量少。
接口隔离原则与单一职责的区别
接口隔离原则与单一职责的审视角度是不相同的。
单一职责要求的是类和接口职责单一,注重的是职责,这是业务逻辑上的划分,而接口隔离原则要求接口的方法尽量少。
例如一个接口的职责可能包含10个方法,这10个方法都放在一个接口中,并且提供给多个模块访问,各个模块按照规定的权限来访问,在系统外通过文档约束“不使用的方法不要访问”,按照单一职责原则是允许的,按照接口隔离原则是不允许的,因为它要求“尽量使用多个专门的接口”。专门的接口就是指提供给每个模块的都应该是单一接口,而不是建立一个庞大的臃肿的接口,容纳所有的客户端访问。
2. 接口隔离原则的规范
接口要尽量小
这是接口隔离原则的核心定义,不出现臃肿的接口(Fat Interface)。
但是“小”是有限度的,根据接口隔离原则拆分接口时,首先必须满足单一职责原则。
接口要高内聚
高内聚就是提高接口、类、模块的处理能力,减少对外的交互。
具体到接口隔离原则就是,要求在接口中尽量少使用public方法,接口是对外的承诺,承诺越少对系统的开发越有利,变更的风险也就越少,同时也有利于降低成本。
接口只提供访问者需要的方法
接口设计要适度
接口的设计粒度越小,系统越灵活,这是不争的事实。但是,灵活的同时也带来了结构的复杂化,开发难度增加,可维护性降低,这不是一个项目或产品所期望看到的,所以接口设计一定要注意适度。
3. 最佳实践
接口隔离原则是对接口的定义,同时也是对类的定义,接口和类尽量使用原子接口或原子类来组装。关于原子该怎么划分,在实践中可以根据以下几个规则来衡量:
- 一个接口只服务于一个子模块或业务逻辑;
- 通过业务逻辑压缩接口中的public方法,接口时常去回顾,尽量让接口达到“满身筋骨肉”,而不是“肥嘟嘟”的一大堆方法;
- 已经被污染了的接口,尽量去修改,若变更的风险较大,则采用适配器模式进行转化处理;
- 每个项目或产品都有特定的环境因素,环境不同,接口拆分的标准就不同。深入了解业务逻辑,根据经验和常识决定接口的粒度大小。接口粒度太小,导致接口数据剧增,开发人员呛死在接口的海洋里;接口粒度太大,灵活性降低,无法提供定制服务,给整体项目带来无法预料的风险
参考文献:《设计模式之禅》