王争《设计模式之美》学习笔记
如何理解“接口隔离原则”?
- SOLID 中的英文字母“I”,接口隔离原则,英文翻译是“ Interface Segregation Principle”,缩写为 ISP。
- 客户端不应该强迫依赖它不需要的接口。
如何理解“接口”二字?
把“接口”理解为一组 API 接口集合
- 文中举例,微服务用户系统提供了一组跟用户相关的 API 给其他系统使用,UserService。
- 现在需要在用户系统中提供一个删除用户接口,可以在UserService中添加deleteUserById()。
- 但是带来了安全隐患,所有使用到 UserService 的系统,都可以调用这个接口,我们只希望通过后台管理系统来执行。
- 可以将删除接口单独放到另外一个接口 RestrictedUserService 中,只提供给后台管理系统来使用。
- 如果部分接口只被部分调用者使用,那我们就需要将这部分接口隔离出来,单独给对应的调用者使用,而不是强迫其他调用者也依赖这部分不会被用到的接口。
- 当然,最好的解决方案是从架构设计的层面,通过接口鉴权的方式来限制接口的调用。
把“接口”理解为单个 API 接口或函数
- 文中举例,包含统计功能的count()函数。
- 那么,求最大值、最小值、平均值等功能就不应该在count()函数中,应该封装成独立的函数max()、min()、average()等。
- 设计的时候,如果每个统计需求都要用到那几个计算,放在count()函数中也算合理。
- 但是,如果不同的统计需求只用到部分计算,还是不同的部分组合,把不同的计算放在不同的函数中,每次统计挑选需要的计算函数,不用每次都过一遍所有的计算,提升系统的性能。
- 接口隔离原则跟单一职责原则有点类似,单一职责原则针对的是模块、类、接口的设计。而接口隔离原则更侧重于接口的设计,思考的角度是通过调用者如何使用接口来间接地判定接口是否职责单一。
把“接口”理解为 OOP 中的接口概念
接口隔离示例
- 文中举例,我们的项目中用到了三个外部系统:Redis、MySQL、Kafka,对应一系列配置信息 RedisConfig、MysqlConfig、KafkaConfig 类。
- 增加 Redis 和 Kafka 配置信息需要热更新的需求:
- 更新接口 Updater 中有一个热更新方法 Updater()
- RedisConfig、KafkaConfig 实现接口 Updater 中的 Updater()
- ScheduledUpdater 类中调用 RedisConfig、KafkaConfig 的 Updater() 方法,以实现更新配置
- 增加 MySQL 和 Redis 监控功能,需要通过HTTP地址访问:
- 展示接口 Viewer 中有一个展示方法 output()
- MysqlConfig、RedisConfig 实现接口Viewer中的 output()
- SimpleHttpServer 类中的 addViewers() 方法,实例化的时候可以添加参数MysqlConfig、RedisConfig
- 上面两个例子中的 Updater 和 Viewer 接口功能单一,ScheduledUpdater 只依赖 Updater,SimpleHttpServer 只依赖 Viewer,都不需要被强迫依赖不需要的接口。
非接口隔离示例
- 还是上文的需求,设计一个大而全的 Config 接口:
- 接口 Config 中有热更新方法 Updater(),展示方法 output()
- RedisConfig、MysqlConfig、KafkaConfig 实现接口 Config 中的所有方法和属性
- ScheduledUpdater 类和 SimpleHttpServer 类中使用全部的配置文件
总结
- 首先,第一种设计思路更加灵活、易扩展、易复用。
- Updater 和 Viewer 接口功能单一,可以很方便复用
- 如果我们增加统计模块,也想展示在网页上,就可以复用 SimpleHttpServer,调用 addViewers() 方法,和配置类没有任何关系
- 其次,第二种设计思路在代码实现上做了一些无用功。
- KafkaConfig 只需要实现 Updater(),不需要实现 output()
- MysqlConfig 只需要实现 output(), 不需要实现 Updater()
- 而第二种设计中,RedisConfig、MysqlConfig、KafkaConfig 需要实现 Config 的所有方法。
- 另外,如果往 Config 中添加一个新的方法,那么所有实现类RedisConfig、MysqlConfig、KafkaConfig 都要改一遍。