设计原则:接口隔离原则(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
拆分成两个IReadProductService
IOperProductService
- 然后我们的
ProductService
实现这两个接口,而CacheProductService
只实现IReadProductService
- 需要缓存的客户端使用
IReadProductService
,不需要缓存的客户端使用IReadProductService
IOperProductService
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):使用多个专门的接口,而不使用单一的总接口,即客户端不应该依赖那些它不需要的接口. 根 ...
随机推荐
- 学习笔记-python基础
一. 1.python按装 1.1 官网 https://www.python.org 1.2 点 downloads下的 windows下载64位python3.7.3版本 Download Win ...
- 华硕主板开机无法进入BIOS
先说下本人情况,自己组装的台式机,华硕TUF B550M-Plus (wifi)的主板,CPU是锐龙 4650G(带核显),其他配件不涉及问题就不提了. 原来用独显的时候,没啥问题,开机有品牌logo ...
- 如何在 C# 8 中使用 Channels
在面对 生产者-消费者 的场景下, netcore 提供了一个新的命名空间 System.Threading.Channels 来帮助我们更高效的处理此类问题,有了这个 Channels 存在, 生产 ...
- 翻译:《实用的Python编程》04_04_Defining_exceptions
目录 | 上一节 (4.3 特殊方法) | 下一节 (5 对象模型) 4.4 定义异常 用户可以通过类实现自定义异常: class NetworkError(Exception): pass **异常 ...
- SpringMVC-06 Ajax
SpringMVC-06 Ajax Ajax 1.简介 AJAX = Asynchronous JavaScript and XML(异步的 JavaScript 和 XML). AJAX 是一种在无 ...
- sprintgboot+springsecurity的跨域问题,
整个项目是使用前后端分离的形式开发,登录接口部分出现了问题, 重写了security的登录接口,返回json数据 到这一步已经没有没有问题了,使用postman测试,也可以看到接口返回的结果,但是使用 ...
- 微服务架构Day16-SpringBoot之监控管理
监控管理使用步骤 通过引入spring-boot-starter-actuator,可以使用SpringBoot提供应用监控和管理的功能.可以通过HTTP,JMX,SSH协议来进行操作,自动得到审计, ...
- 利用jmeter对WebRTC应用进行压力测试(java)
利用jmeter对WebRTC应用进行压力测试(java) 说明:WebRTC是一款开源的多人即时视频API,与一般的http请求不同,webrtc应用实际压力主要是码流 最近负责了一个WebRTC的 ...
- react第三方库
作者:慕课网链接:https://www.zhihu.com/question/59073695/answer/1071631250来源:知乎著作权归作者所有.商业转载请联系作者获得授权,非商业转载请 ...
- sort函数用于vector向量的排序
参考资料: 关于C++中vector和set使用sort方法进行排序 作者注:这篇文章写得相当全面,包括对vector和set中不同数据类型(包括结构体)的排序,还有一些还没看懂--特作此摘录,供当前 ...