编程进阶网编程进阶网
  • 基础组成体系
  • 程序编程原理
  • 异常和IO系统
  • 六大设计原则
  • 设计模式导读
  • 创建型设计模式
  • 结构型设计模式
  • 行为型设计模式
  • 设计模式案例
  • 面向对象思想
  • 基础入门
  • 高级进阶
  • JVM虚拟机
  • 数据集合
  • Java面试题
  • C语言入门
  • C综合案例
  • C标准库
  • C语言专栏
  • C++入门
  • C++综合案例
  • C++专栏
  • HTML
  • CSS
  • JavaScript
  • 前端专栏
  • Swift
  • iOS入门
  • 基础入门
  • 开源库解读
  • 性能优化
  • Framework
  • 方案设计
  • 媒体音视频
  • 硬件开发
  • Groovy
  • 常用工具
  • 大厂面试题
  • 综合案例
  • 网络底层
  • Https
  • 网络请求
  • 故障排查
  • 专栏
  • 数组
  • 链表
  • 栈
  • 队列
  • 树
  • 递归
  • 哈希
  • 排序
  • 查找
  • 字符串
  • 其他
  • Bash脚本
  • Linux入门
  • 嵌入式开发
  • 代码规范
  • Markdown
  • 开发理论
  • 开发工具
  • Git管理
  • 百宝箱
  • 开源协议
  • 技术招聘
  • 测试经验
  • 职场提升
  • 技术模版
  • 关于我
  • 目标清单
  • 学习框架
  • 育儿经验
  • 我的专栏
  • 底层能力
  • 读书心得
  • 随笔笔记
  • 职场思考
  • 中华历史
  • 经济学故事
  • 基础组成体系
  • 程序编程原理
  • 异常和IO系统
  • 六大设计原则
  • 设计模式导读
  • 创建型设计模式
  • 结构型设计模式
  • 行为型设计模式
  • 设计模式案例
  • 面向对象思想
  • 基础入门
  • 高级进阶
  • JVM虚拟机
  • 数据集合
  • Java面试题
  • C语言入门
  • C综合案例
  • C标准库
  • C语言专栏
  • C++入门
  • C++综合案例
  • C++专栏
  • HTML
  • CSS
  • JavaScript
  • 前端专栏
  • Swift
  • iOS入门
  • 基础入门
  • 开源库解读
  • 性能优化
  • Framework
  • 方案设计
  • 媒体音视频
  • 硬件开发
  • Groovy
  • 常用工具
  • 大厂面试题
  • 综合案例
  • 网络底层
  • Https
  • 网络请求
  • 故障排查
  • 专栏
  • 数组
  • 链表
  • 栈
  • 队列
  • 树
  • 递归
  • 哈希
  • 排序
  • 查找
  • 字符串
  • 其他
  • Bash脚本
  • Linux入门
  • 嵌入式开发
  • 代码规范
  • Markdown
  • 开发理论
  • 开发工具
  • Git管理
  • 百宝箱
  • 开源协议
  • 技术招聘
  • 测试经验
  • 职场提升
  • 技术模版
  • 关于我
  • 目标清单
  • 学习框架
  • 育儿经验
  • 我的专栏
  • 底层能力
  • 读书心得
  • 随笔笔记
  • 职场思考
  • 中华历史
  • 经济学故事
  • 01.面向对象六大原则
  • 02.单一职责原则详解
  • 03.开闭原则详细介绍
  • 04.里式替换原则介绍
  • 05.接口隔离原则介绍
  • 06.依赖倒置原则介绍
  • 07.迪米特原则介绍
  • 08.代码保持简单原则
  • 09.DRY原则简单介绍

05.接口隔离原则介绍

目录介绍

  • 01.问题思考的分析
  • 02.学习接口隔离目标
  • 03.理解接口隔离原则
  • 04.接口隔离原则思想
  • 05.一组API接口集合案例
  • 06.单个API接口或函数
  • 07.总结接口隔离原则
  • 08.思考一道课后题

01.问题思考的分析

  1. 什么叫作接口隔离法则,它和面向对象中的接口有何区别?

02.学习接口隔离目标

学习了 SOLID 原则中的单一职责原则、开闭原则和里式替换原则,今天我们学习接口隔离原则。它对应 SOLID 中的英文字母“I”。

对于这个原则,最关键就是理解其中“接口”的含义。那针对“接口”,不同的理解方式,对应在原则上也有不同的解读方式。

除此之外,接口隔离原则跟我们之前讲到的单一职责原则还有点儿类似,所以今天我也会具体讲一下它们之间的区别和联系。

03.理解接口隔离原则

接口隔离原则的英文翻译是“ Interface Segregation Principle”,缩写为 ISP。 Robert Martin 在 SOLID 原则中是这样定义它的:“Clients should not be forced to depend upon interfaces that they do not use。”

直译成中文的话就是:客户端不应该强迫依赖它不需要的接口。其中的“客户端”,可以理解为接口的调用者或者使用者。

实际上,“接口”这个名词可以用在很多场合中。在软件开发中,我们既可以把它看作一组抽象的约定,也可以具体指系统与系统之间的 API 接口,还可以特指面向对象编程语言中的接口等。

前面我提到,理解接口隔离原则的关键,就是理解其中的“接口”二字。在这条原则中,我们可以把“接口”理解为下面三种东西:

  1. 一组 API 接口集合
  2. 单个 API 接口或函数
  3. OOP 中的接口概念

04.接口隔离原则思想

接口隔离原则的核心思想是:

  1. 接口要小而专一:接口不应该包含太多功能,应该拆分成多个小接口,每个接口只定义客户端需要的方法。
  2. 避免臃肿接口:如果接口定义了过多的方法,某些客户端可能只需要其中一部分功能,这会导致不必要的依赖,增加代码的复杂性。

通俗地说,一个接口只应该包含客户端真正需要的功能,其他无关的功能应该分离到不同的接口中。

05.一组API接口集合案例

5.1 错误示范案例

假设我们在电商系统中设计了一个用户操作接口UserOperations,包含了用户的所有操作:

interface UserOperations {
    void createOrder();
    void cancelOrder();
    void browseProducts();
    void manageAccount();
    void applyDiscount();
}

在这个设计中,所有的用户操作都集中在一个接口中,但并不是所有用户都需要所有这些操作。

比如,对于普通用户,可能只需要浏览商品、创建订单和管理账户的功能,而对于管理员用户,则可能更关注于应用折扣和订单管理的功能。

这种设计违反了接口隔离原则,导致客户端依赖了很多不需要的方法,增加了系统的复杂性和维护成本。

5.2 正确示范案例

为了遵循接口隔离原则,我们可以将UserOperations接口拆分成多个更小、更专一的接口:

 interface OrderOperations {
    void createOrder();
    void cancelOrder();
}

interface ProductOperations {
    void browseProducts();
}

interface AccountOperations {
    void manageAccount();
}

interface AdminOperations {
    void applyDiscount();
}

然后根据不同的用户类型,实现各自所需的接口:

class NormalUser implements OrderOperations, ProductOperations, AccountOperations {
    @Override
    public void createOrder() {
        // 创建订单逻辑
    }

    @Override
    public void cancelOrder() {
        // 取消订单逻辑
    }

    @Override
    public void browseProducts() {
        // 浏览商品逻辑
    }

    @Override
    public void manageAccount() {
        // 管理账户逻辑
    }
}

class AdminUser implements AdminOperations, OrderOperations {
    @Override
    public void applyDiscount() {
        // 应用折扣逻辑
    }

    @Override
    public void createOrder() {
        // 创建订单逻辑
    }

    @Override
    public void cancelOrder() {
        // 取消订单逻辑
    }
}

这种设计方式将接口职责进行了合理的划分,使得每个接口只包含客户端真正需要的方法,符合接口隔离原则的要求。

在电商系统中,遵循接口隔离原则可以确保用户操作接口不会变得臃肿,每个用户类型只依赖于它们需要的功能接口,从而避免了不必要的依赖和实现负担。

5.3 电商案例总结

问题:接口臃肿

在电商系统中,如果我们为用户设计了一个包含所有操作的接口,会导致接口臃肿,增加了实现类的复杂性。

客户端在实现时必须实现所有方法,即使它们中的某些方法在当前场景中不需要使用。

解决方式:将大接口拆分成多个小接口,每个接口只包含相关的方法。这样可以提高代码的可维护性和灵活性,减少不必要的依赖。

06.单个API接口或函数

现在我们再换一种理解方式,把接口理解为单个接口或函数(以下为了方便讲解,我都简称为“函数”)。

那接口隔离原则就可以理解为:函数的设计要功能单一,不要将多个不同的功能逻辑在一个函数中实现。接下来,我们还是通过一个例子来解释一下。

public class Statistics {
  private Long max;
  private Long min;
  private Long average;
  private Long sum;
  private Long percentile99;
  private Long percentile999;
  //...省略constructor/getter/setter等方法...
}

public Statistics count(Collection<Long> dataSet) {
  Statistics statistics = new Statistics();
  //...省略计算逻辑...
  return statistics;
}

在上面的代码中,count() 函数的功能不够单一,包含很多不同的统计功能,比如,求最大值、最小值、平均值等等。

按照接口隔离原则,我们应该把 count() 函数拆成几个更小粒度的函数,每个函数负责一个独立的统计功能。拆分之后的代码如下所示:

public Long max(Collection<Long> dataSet) { //... }
public Long min(Collection<Long> dataSet) { //... } 
public Long average(Colletion<Long> dataSet) { //... }
// ...省略其他统计函数...

不过,你可能会说,在某种意义上讲,count() 函数也不能算是职责不够单一,毕竟它做的事情只跟统计相关。

在讲单一职责原则的时候,也提到过类似的问题。实际上,判定功能是否单一,除了很强的主观性,还需要结合具体的场景。

如果在项目中,对每个统计需求,Statistics 定义的那几个统计信息都有涉及,那 count() 函数的设计就是合理的。相反,如果每个统计需求只涉及 Statistics 罗列的统计信息中一部分,比如,有的只需要用到 max、min、average 这三类统计信息,有的只需要用到 average、sum。而 count() 函数每次都会把所有的统计信息计算一遍,就会做很多无用功,势必影响代码的性能,特别是在需要统计的数据量很大的时候。所以,在这个应用场景下,count() 函数的设计就有点不合理了,我们应该按照第二种设计思路,将其拆分成粒度更细的多个统计函数。

接口隔离原则跟单一职责原则有点类似,不过稍微还是有点区别。单一职责原则针对的是模块、类、接口的设计。

接口隔离原则相对于单一职责原则,一方面它更侧重于接口的设计,另一方面它的思考的角度不同。它提供了一种判断接口是否职责单一的标准:通过调用者如何使用接口来间接地判定。如果调用者只使用部分接口或接口的部分功能,那接口的设计就不够职责单一。

07.总结接口隔离原则

如何理解“接口隔离原则”? 理解“接口隔离原则”的重点是理解其中的“接口”二字。这里有三种不同的理解。

  1. 如果把“接口”理解为一组接口集合,可以是某个微服务的接口,也可以是某个类库的接口等。如果部分接口只被部分调用者使用,我们就需要将这部分接口隔离出来,单独给这部分调用者使用,而不强迫其他调用者也依赖这部分不会被用到的接口。
  2. 如果把“接口”理解为单个 API 接口或函数,部分调用者只需要函数中的部分功能,那我们就需要把函数拆分成粒度更细的多个函数,让调用者只依赖它需要的那个细粒度函数。
  3. 如果把“接口”理解为 OOP 中的接口,也可以理解为面向对象编程语言中的接口语法。那接口的设计要尽量单一,不要让接口的实现类和调用者,依赖不需要的接口函数。

接口隔离原则与单一职责原则的区别

  1. 单一职责原则针对的是模块、类、接口的设计。接口隔离原则相对于单一职责原则,一方面更侧重于接口的设计,另一方面它的思考角度也是不同的。
  2. 接口隔离原则提供了一种判断接口的职责是否单一的标准:通过调用者如何使用接口来间接地判定。如果调用者只使用部分接口或接口的部分功能,那接口的设计就不够职责单一。

08.思考一道课后题

java.util.concurrent 并发包提供了 AtomicInteger 这样一个原子类,其中有一个函数 getAndIncrement() 是这样定义的:给整数增加一,并且返回未増之前的值。

我的问题是,这个函数的设计是否符合单一职责原则和接口隔离原则?为什么?

/**
 * Atomically increments by one the current value.
 * @return the previous value
 */
public final int getAndIncrement() {//...}
贡献者: yangchong211
上一篇
04.里式替换原则介绍
下一篇
06.依赖倒置原则介绍