设计原则:接口隔离原则(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):使用多个专门的接口,而不使用单一的总接口,即客户端不应该依赖那些它不需要的接口. 根 ...
随机推荐
- 一条sql语句的执行过程
一条select语句执行流程 第一步:连接器 连接器负责跟客户端建立连接.获取权限.维持和管理连接.如果用户名密码验证通过后,连接器会到权限表里面查出你拥有的权限.之后该连接的权限验证都依赖于刚查出来 ...
- hadoop环境搭建:高可用
目录 1.硬件配置 2.软件版本 3.准备工作 3.1.配置网络环境 3.2.安装JDK 3.3.安装ZOOKEEPER 4.安装Hadoop 5.启动 6.问题 7.配置文件 1.硬件配置 采用3台 ...
- getter和setter以及defineProperty的用法
getter 和 setter 和 defineProperty getter:将对象属性绑定到查询该属性时将被调用的函数 说人话就是,当你调用一个getter属性时会调用定义好的get函数,这个函数 ...
- JavaScript async/await:优点、陷阱及如何使用
翻译练习 原博客地址:JavaScript async/await: The Good Part, Pitfalls and How to Use ES7中引进的async/await是对JavaSc ...
- 通过webhost扩展方式初始化EFCore数据库
通过webhost扩展方式初始化EFCore数据库 EFCore数据库初始化 1.定义WebHostMigrationExtensions类 public static class WebHostM ...
- wxWidgets源码分析(2) - App主循环
目录 APP主循环 MainLoop 消息循环对象的创建 消息循环 消息派发 总结 APP主循环 MainLoop 前面的wxApp的启动代码可以看到,执行完成wxApp::OnInit()函数后,接 ...
- iPhone去除input默认样式
/*<!---->去掉苹果短的样式*/ input[type="button"], input[type="submit"], input[type ...
- 虚拟机测试cobbler,网络安装加载最后出现 dracut:/#
1.cobbler的几个重要概念: distro:发行版系统容,我理解为镜像来源,提供了kernel 和 initrd 文件以及repo源 profile:kickstart文件,用于定制系统,定制安 ...
- Java 集合框架 03
集合框架·HashSet 和 TreeSet HashSet存储字符串并遍历 * A:Set集合概述及特点 * 通过API查看即可 * 无索引,不可以重复,无序 * B:案例演示 * HashSet存 ...
- python-链队列的实现
7 class Node(object): 8 def __init__(self,data): 9 self.data = data 10 self.next = None 11 12 class ...