设计原则:接口隔离原则(ISP)
接口隔离原则的英文是Interface Segregation Principle,缩写就是ISP。与里氏替换原则一样其定义同样有两种
定义1:
Clients should not be forced to depend upon interfaces that they don'tuse.
(客户端被强迫不应该依赖它不需要的接口。)
定义2:
The dependency of one class to another one should depend on the smallest possibleinterface.
(类间的依赖关系应该建立在最小的接口上。)
这两种定义相比较,我更喜欢它的第一种定义。其中最重要的概念就是“接口”,这里的接口其实不仅仅是指OOP概念中的接口,其小到类所暴露出来的public方法、所提供的公共属性,大到业务上一组API的组合,甚至系统对外所提供的服务,都可以称之为接口,接口是一种抽象的约定。而客户端可以理解为接口的调用者或者使用者。
这个表述看起来很容易,但是在真正设计中却很难,在大多数的项目里,也是经常被违背的原则之一,因为设计者往往很难站在使用者的角度上去看待问题,甚至有很多设计者根本没有接口的概念,他们往往只从类的角度上去思考问题,而在类设计完毕后,而为了使用接口象征性的增加一个接口,然后把类的方法签名搬到接口里而已。(我们可以想想,自己在项目中是不是也是先写类,后写接口呢?)
2.如何理解并运用好接口隔离原则
接口隔离原则要求我们尽量提供小而美的接口,而不是一个庞大臃肿的接口,以试图满足所有的调用者使用,它是对接口的的一种规范和约束。
其实在设计中想要运用好接口隔离原则,有一个好的办法,就是需要我们站在使用者的角度上去思考问题,按需去设计接口,我们可以通过几个例子来看一下
2.1 OOP中的接口隔离原则
现在,我们有一个商品系统,我想绝对多数的系统中都会按照下面这种方式进行接口的设计

它提供了CRUD操作供客户端调用。随着业务的不断发展壮大,我们发现商品访问的性能越来越差,数据库的压力也越来越大,这时我们需要对商品系统增加缓存的功能,但是有些场景下我们又需要能够实时的查询到商品系统,这种场景下应该怎么办?
public ProductInfo get(String id) {
if(cache.contains(key)){
return cache.get(key);
}
return productRepository.get()
}
public ProductInfo get(String id,boolean isCache) {
if(cache.contains(key)&&isCache){
return cache.get(key);
}
return productRepository.get()
}
这时许多人的做法可能是增加一个参数isCache由客户端传入来标记是否需要读取缓存,不得不说这真是一个馊主意,因为这违背了一个最基础的原则——开闭原则,它会给我们后续维护带来很大的灾难。
也有一些人可能会想到我提供一个CacheProductService 也实现一下IProductService 在这个服务里面做缓存的功能,这样需要缓存的客户端就实例化CacheProductService 不需要缓存的客户端就还是实例化原来的 ProductService
public class CacheProductService implements IProductService {
@Override
public ProductInfo get(String id) {
if(cache.contains(id)){
return cache.get(id);
}
return productRepository.get(id);
}
@Override
public List<ProductInfo> getList() {
if(cache.contains("product-key")){
return cache.get("product-key");
}
return productRepository.getList();
}
@Override
public void create(ProductInfo productInfo) { }
@Override
public void modify(ProductInfo productInfo) { }
@Override
public void delete(String id) { }
}
就像我上面写的这样,但这样又有一些问题,首先这样的设计违背了里式替换原则,再者增删改操作并不需要缓存。
那么到底应该如何去做呢?这个时候我们可以利用接口隔离原则,
- 把原来的
IProductService拆分成两个IReadProductServiceIOperProductService - 然后我们的
ProductService实现这两个接口,而CacheProductService只实现IReadProductService - 需要缓存的客户端使用
IReadProductService,不需要缓存的客户端使用IReadProductServiceIOperProductService

public class ProductService implements IReadProductService,IOperProductService {
@Override
public ProductInfo get(String id) {
return productRepository.get(id);
}
@Override
public List<ProductInfo> getList() {
return productRepository.getList();
}
@Override
public void create(ProductInfo productInfo) {
productRepository.create(productInfo);
}
@Override
public void modify(ProductInfo productInfo) {
productRepository.modify(productInfo);
}
@Override
public void delete(String id) {
productRepository.delete(id);
}
}
public class CacheProductService implements IReadProductService {
@Override
public ProductInfo get(String id) {
if(cache.contains(id)){
return cache.get(id);
}
return productRepository.get(id);
}
@Override
public List<ProductInfo> getList() {
if(cache.contains("product-key")){
return cache.get("product-key");
}
return productRepository.getList();
}
}
甚至在客户端和设计需要的情况下我们可以把简单的CRUD接口拆分最喜欢的拆分成但方法接口ICreate IModify IDelete IRead。接口的规模越小,其复用性和灵活性也就越高,但我们必须注意一点那就是按需设计,因为复用性和灵活性增加的同时,必然也会带来增加系统的复杂度,降低可读性等问题,因此我们必须要掌握好设计的度。
public interface ICreate<T> {
void create(T t);
}
public interface IModify<T> {
void modify(T t);
}
public interface IDelete {
void delete(String id);
}
public interface IRead<T> {
T get(String id);
List<T> getList();
}
public class ProductService implements ICreate<ProductInfo>,IModify<ProductInfo>,IDelete,IRead<ProductInfo>{
}
2.3 类设计中的接口隔离原则
其实接口隔离原则的应用不应该局限于OOP中的"接口",我们不应被接口所迷惑,接口隔离原则中的“接口”,更像是一种约定,因此在类的设计中我们同样应该遵循接口原则,因为类所提供的一些公共方法也是一种约定。
比如我们在监控、统计等系统中通常会用到各个指标的统计,比如均值、求和、最大值、中位数.......,这时我们设计了一个Indicator类,里面提供了sum avg p50 p95等属性,然后提供了一个compute方法来计算各个指标的值,最后返回一个Indicator对象。
public class Indicator {
private Long sum;
private Long min;
private Long avg;
private Long p50;
private Long p95;
private Long p99;
public Indicator compute(List<Long> list){
Indicator indicator=new Indicator();
//...
return indicator;
}
}
我们想一想这样会不会有问题,如果我们所有的调用者都需要所有的指标,这样设计并没有什么问题,但如果有些调用者可能仅仅需要其中的某一个或者几个指标就会有问题了。因为,如果我只需要其中一个指标,但却计算了所有的指标值,浪费时间性能不说,一旦其中某一个指标计算过程中除了错误,就会导致我连其它几个指标都拿不到。这样客户端就依赖了自己所不需要的东西,违背了接口隔离的原则。
这时我们可以考虑把各个指标的计算分开来
public Long min(List<Long> list){ }
public Long max(List<Long> list){ }
public Long avg(List<Long> list){ }
......
这样看起来接口隔离原则跟单一职责原则有些相似,但其实是有不同的,单一职责原则主要是针对模块、类、方法的设计,注重职责的单一,而接口隔离原则更注重站在调用者的角度上看约定是否存在自己所不需要的东西,它要求给每个使用者都按需提供接口,而不是建立一个庞大臃肿的接口以供所有调用者使用。
2.3 系统设计中的接口隔离原则
不仅仅是在OOP中的接口和类的设计要遵循接口隔离原则,在系统对外所提供的API的设计中,我们同样应该遵循接口隔离原则。
例如在用户系统的设计中,多数人都会提供一个用户API,然后这个API提供了一个大而全的接口列表。create、modify、delete、get .......

但有些场景下并不合理的,因为这是站在服务提供者的角度上进行设计的。如果你的用户服务仅仅是提供给后台管理系统使用那么并没有问题,但是如果同时也提供给登录系统使用那么就会有问题了,因为登录系统可能只需要登录注册两个操作,那么对于登录系统的来说用户服务就提供了它所不需要的接口。这样以来我们对登录系统暴漏了删除和修改接口增加了系统风险。
此时,我们应该对登录系统单独的提供一组API接口。如下图所示

3 总结
大而全的东西存在了太多的不确定性,在接口的设计中,我们应该遵循接口隔离原则,尽量提供小而美的接口。但同时我们也应该注意设计要适度,因为越小的东西就越灵活,但如果过于小又会增加系统的复杂性。
接口隔离原则强调了客户端被强迫不应该依赖它不需要的接口。它的应用不应局限于简单的OOP接口,小到类、方法的设计,大到系统之间的交互......接口隔离原则都可以指导我们进行更好的设计。
系列文章
关注下方公众号,回复“代码的艺术”,可免费获取重构、设计模式、代码整洁之道等提升代码质量等相关学习资料
设计原则:接口隔离原则(ISP)的更多相关文章
- 设计模式之六大原则——接口隔离原则(ISP)
设计模式之六大原则——接口隔离原则(ISP) 转载于:http://www.cnblogs.com/muzongyan/archive/2010/08/04/1792528.html 接口隔离原则 ...
- 设计模式值六大原则——接口隔离原则 (ISP)
接口隔离原则 Interface Segregation Principle 定义: 客户端不应该依赖它不需要的接口 类间的依赖关系应该建立在最小的接口上 我们可以把这两个定义概括为一句话:建立 ...
- Java设计原则—接口隔离原则(转)
接口隔离原则 Interface Segregation Principle 定义: 客户端不应该依赖它不需要的接口 类间的依赖关系应该建立在最小的接口上 我们可以把这两个定义概括为一句话:建立 ...
- C# 实例解释面向对象编程中的接口隔离原则
在面向对象编程中,SOLID 是五个设计原则的首字母缩写,旨在使软件设计更易于理解.灵活和可维护.这些原则是由美国软件工程师和讲师罗伯特·C·马丁(Robert Cecil Martin)提出的许多原 ...
- C#软件设计——小话设计模式原则之:接口隔离原则ISP
前言:有朋友问我,设计模式原则这些东西在园子里都讨论烂了,一搜一大把的资料,还花这么大力气去整这个干嘛.博主不得不承认,园子里确实很多这方面的文章,并且不乏出色的博文.博主的想法是,既然要完善知识体系 ...
- 第2章 面向对象的设计原则(SOLID):4_接口隔离原则(ISP)
4. 接口隔离原则(Interface Segregation Principle,ISP) 4.1 定义 (1)使用多个专门的接口,而不使用单一的总接口,即客户端不应该依赖那些它不需要的接口.类间的 ...
- 【面向对象设计原则】之接口隔离原则(ISP)
接口隔离原则(Interface Segregation Principle, ISP):使用多个专门的接口,而不使用单一的总接口,即客户端不应该依赖那些它不需要的接口. 从接口隔离原则的定义可以看 ...
- 六大设计原则(四)ISP接口隔离原则(上)
ISP的定义 首先明确接口定义 实例接口 我们在Java中,一个类用New关键字来创建一个实例.抛开Java语言我们其实也可以称为接口.假设Person zhangsan = new Person() ...
- 面象对象设计原则之四:接口隔离原则(The Interface Segregation Principle,ISP)
接口隔离原则定义如下: 接口隔离原则(Interface Segregation Principle, ISP):使用多个专门的接口,而不使用单一的总接口,即客户端不应该依赖那些它不需要的接口. 根 ...
随机推荐
- C++算法代码——统计数字
题目来自:http://218.5.5.242:9018/JudgeOnline/problem.php?id=1109 题目描述 某次科研调查时得到了n个自然数,每个数均不超过1500000000( ...
- react性能提升
1.把.bind(this)提升到constructor里面 2.在生命周期函数里面shouldComponentupdate里面做父组件改变重新渲染以致于子组件重新渲染的禁止 3.在setstate ...
- Project facet Java version 1.7 is not supported.解决方法
最近遇到这个问题,在网上查到的解决方案基本都是下面几个: 1.右击项目,properties,project facets,改动java的version为1.7. 2.window,propertie ...
- Spring注解@PropertySource加载配置文件和SpringBoot注解@Value、@ConfigurationProperties进行属性映射
SpringBoot的配置文件 位置:resources目录下 配置文件的作用: (1).SpringBoot是基于约定的,所以很多配置都有默认值,但如果想使用自己的配置替换默认配置的话,就可以使用a ...
- hutool的DateUtil工具类
1.0.DateUitl(日期时间) 0)坐标 <dependency> <groupId>cn.hutool</groupId> <artifactId&g ...
- Spring Boot集成Springfox Swagger3和简单应用
摘要:Springfox Swagger可以动态生成 API 接口供前后端进行交互和在线调试接口,Spring Boot 框架是目前非常流行的微服务框架,所以,在Spring Boot 项目中集成Sp ...
- pdf转换成文本解决格式不统一问题
pdf转换成文本解决格式不统一问题 懒得调OCR服务了,所以快速解决的方法是: pdf转png:https://pdf2png.com/zh/ png转统一格式pdf:adobe acrobat自带增 ...
- 身份认证:JSON Web Token
JSON Web Token(JWT)是一种基于JSON的开放标准((RFC 7519),也是目前最流行的跨域认证解决方案. 传统的 cookie 认证方式看起来遵守了 REST 架构的无状态要求,但 ...
- Java的特性和优势以及不同版本的分类,jdk,jre,jvm的联系与区别,javadoc的生成
Java 1.Java的特性和优势 Write Once,Run Anywhere 简单性 面向对象 可移植性 高性能 分布式 动态性 多线程 安全性 健壮性 2.Java的三大版本 JavaSE:标 ...
- css实现鼠标滑过出现从中间向两边扩散的下划线
这个效果一开始我是在华为商城页面上看到的,刚开始还以为挺复杂,实现的时候还有点没头绪.不过,还好有百度,借此记录一下我在导航条上应用的实现方法. 主要是借助了伪元素,代码如下: <div cla ...