接口隔离原则【Interface Segregation Principle】
一、定义
讲接口隔离原则,它有两种定义:
第一种定义: Clients should not beforced to depend upon interfaces that they don't use.
客户端不应该依赖它不需用的接口。
第二种定义:The dependency of oneclass to another one should depend on the smallest possible interface。
类间的依赖关系应该建立在最小的接口上。
二、解释
第一种定义客户端不应该依赖它不需要接口,那依赖什么?依赖它需要的接口,客户端需要什么接口就提供什么接口,把不需要的接口剔除掉,那就需要对接口进行细化,保证其纯洁性;
第二个定义,类间的依赖关系应该建立在最小的接口上,它要求是最小的接口,也是要求接口细化,接口纯洁,与第一个定义如出一辙,只是一个事物的两种不同描述。
我们可以把这两个定义概括为一句话:建立单一接口,不要建立臃肿庞大的接口。再通俗的一点讲:接口尽量细化,同时接口中的方法尽量的少。看到这里大家有可能要疑惑了,这与单一职责原则不是相同的吗?错,接口隔离原则与单一职责的定义的规则是不相同的,单一职责要求的是类和接口职责单一,注重的是职责,没有要求接口的方法减少,例如一个职责可能包含10个方法,这10个方法都放在一个接口中,并且提供给多个模块访问,各个模块按照规定的权限来访问,在系统外通过文档约束不使用的方法不要访问,按照单一职责原则是允许的,按照接口隔离原则是不允许的,因为它要求“尽量使用多个专门的接口”,专门的接口指什么?就是指提供给多个模块的接口,提供给几个模块就应该有几个接口,而不是建立一个庞大的臃肿的接口,所有的模块可以来访问。
三、应用
我们来举个例子来说明接口隔离原则到底对我们提出了什么要求。我们今天来定义一下什么是美女:首先要面貌好看,其次是身材要窈窕,然后要有气质,当然了,这三者各人的排列顺序不一样,总之要成为一名美女就必须具备:面貌、身材和气质
我们用类图类体现一下星探找美女的过程,看类图:
定义了一个IPettyGirl接口,声明所有的美女都应该有goodLooking、niceFigure和greatTemperament,然后又定义了一个抽象类AbstractSearcher,其作用就是搜索美女然后展示信息,只要美女都是按照这个规范定义,Searcher(星探)就轻松的多了,我们先来看美女的定义:
IPettyGirl.java
public interface IPettyGirl {
// 要有姣好的面孔
public void goodLooking();
// 要有好身材
public void niceFigure();
// 要有气质
public void greatTemperament();
}
PettyGirl.java
public class PettyGirl implements IPettyGirl {
private String name;
// 美女都有名字
public PettyGirl(String _name) {
this.name = _name;
}
// 脸蛋漂亮
public void goodLooking() {
System.out.println(this.name + "---脸蛋很漂亮!");
}
// 气质要好
public void greatTemperament() {
System.out.println(this.name + "---气质非常好!");
}
// 身材要好
public void niceFigure() {
System.out.println(this.name + "---身材非常棒!");
}
}
然后我们来看AbstractSearcher类,这个类一般就是指星探这个行业了,源代码如下:
AbstractSearcher.java
public abstract class AbstractSearcher {
protected IPettyGirl pettyGirl;
public AbstractSearcher(IPettyGirl _pettyGirl) {
this.pettyGirl = _pettyGirl;
}
// 搜索美女,列出美女信息
public abstract void show();
}
星探查找到美女,打印出美女的信息,源码如下:
Searcher.java
public class Searcher extends AbstractSearcher {
public Searcher(IPettyGirl _pettyGirl) {
super(_pettyGirl);
}
// 展示美女的信息
public void show() {
System.out.println("--------美女的信息如下:---------------");
// 展示面容
super.pettyGirl.goodLooking();
// 展示身材
super.pettyGirl.niceFigure();
// 展示气质
super.pettyGirl.greatTemperament();
}
}
场景类 Client.java
public class Client {
// 搜索并展示美女信息
public static void main(String[] args) {
// 定义一个美女
IPettyGirl yanYan = new PettyGirl("嫣嫣");
AbstractSearcher searcher = new Searcher(yanYan);
searcher.show();
}
}
运行结果如下:
--------美女的信息如下:--------------- 嫣嫣---脸蛋很漂亮! 嫣嫣---身材非常棒! 嫣嫣---气质非常好! |
星探寻找美女的程序我们就开发完毕了,我们来想想这个程序有没有问题,思考一下IPettyGirl这个接口,这个接口是否做到了最优秀的设计。
我们的审美观点都在改变,美女的定义也在变化。就现在,你发现有后一个女孩,脸蛋不怎么样,身材也一般般,但是气质非常好,我相信大部分人都会把这样的女孩叫美女,审美素质提升了,但是我们接口却定义了美女必须是三者都具备呀,可能你要说了,我重新扩展一个美女类,只实现greatTemperament方法其他两个方法置空,什么都不写,不就可以了吗?聪明,但是行不通!为什么呢?星探AbstractSearcher依赖的是IPettyGirl接口,它有三个方法,你只实现了两个方法,星探的方法是不是要修改?我们上面的程序打印出来的信息少了两条,还让星探怎么去辨别是不是美女呢?
好了,我们发现我们的接口IPettyGirl接口设计是有缺陷地,过于庞大了,容纳了一些可变的因素,根据接口隔离原则,星探AbstractSearcher应该依赖与具有部分特质的女孩子,而我们却把这些特质都封装了起来,放到了一个接口中了,封装过渡了!问题查找到了,我们重新修改一下类图:把一个臃肿的美女接口变更为两个独立的接口外形美女接口和气质美女接口
外形美的美女
public interface IGoodBodyGirl {
// 要有姣好的面孔
public void goodLooking();
// 要有好身材
public void niceFigure();
}
气质美的美女
public interface IGreatTemperamentGirl {
// 要有气质
public void greatTemperament();
}
实现类没有改变,只是实现类两个接口,源码如下:
public class PettyGirl implements IGoodBodyGirl, IGreatTemperamentGirl {
private String name;
// 美女都有名字
public PettyGirl(String _name) {
this.name = _name;
}
// 脸蛋漂亮
public void goodLooking() {
System.out.println(this.name + "---脸蛋很漂亮!");
}
// 气质要好
public void greatTemperament() {
System.out.println(this.name + "---气质非常好!");
}
// 身材要好
public void niceFigure() {
System.out.println(this.name + "---身材非常棒!");
}
}
不管以后是要外形美的美女还是气质美的美女都可以轻松的通过PettyGirl定义。
通过这样的改造以后,不管以后是要气质美女还是要外形美女,都可以保持接口的稳定
以上把一个臃肿的接口变更为两个独立的接口依赖的原则就是接口隔离原则,让AbstractSearcher依赖两个专用的接口比依赖一个综合的接口要灵活。接口是我们设计时对外提供的契约,通过分散定义多个接口,可以预防未来变更的扩散,提高系统的灵活性和可维护性。
四、含义
接口隔离原则是对接口进行规范约束,其包含以下四层含义:
1.接口尽量要小。这是接口隔离原则的核心定义,不出现臃肿的接口(Fat Interface),但是“小”是有限度的,首先就是不能违反单一职责原则
2.接口要高内聚。什么是高内聚?高内聚就是提高接口、类、模块的处理能力,减少对外的交互,就比如一个人,你告诉下属“到奥巴马的办公室偷一个XX文件”,然后就听到下属就坚定的口吻回答你“好的,保证完成!”,然后一个月后还真的把XX文件放到你的办公桌了,这种不讲任何条件、立刻完成任务的行为就是高内聚的表现。具体到接口隔离原则就是要求在接口中尽量少公布public方法,接口是对外的承诺,承诺越少对系统的开发越有利,变更的风险也就越少,同时也有利于降低成本。
3.定制服务。一个系统或系统内的模块之间必然会有耦合,有耦合就要相互访问的接口(并不一定就是Java中定义的Interface,也可能是一个类或者是单纯的数据交换),我们设计时就需要给各个访问者(也就客户端)定制服务.定制服务,单独为一个个体提供优良优良的服务。我们在做系统设计时也需要考虑对系统之间或模块之间的定义要采用定制服务,采用定制服务就必然有一个要求就是:只提供访问者需要的方法。
我们就把这个接口进行重构:把IBookSearcher拆分为两个接口
提供给管理人员的实现类同时实现ISimpleBookSearcher和IComplexBookSearcher两个接口,原有程序不用任何改变,而提供给公网的接口变为ISimpleBookSearcher,只允许进行简单的查询,单独为它定制服务。
4、接口设计是有限度的。接口的设计粒度是越小系统越灵活,这是不争的事实,但是这就带来的结构的复杂化,开发难度增加,维护性降低,这不是一个项目或产品所期望看到的,所有接口设计一定要注意适度,适度的“度”怎么来判断的呢?根据经验和常识判断!
接口隔离原则是对接口的定义也同时是对类的定义,接口和都尽量使用原子接口或原子类来组装,但是这个原子该怎么划分是这个模式也是设计中的一大难题,在实践中应用时可以根据以下几个规则来衡量:
一个接口只服务于一个子模块或者业务逻辑。
通过业务逻辑压缩接口中的public方法。接口时常去回顾,尽量做让接口达到“满身筋骨肉”,而不是“肥嘟嘟”的一大堆方法。
已经被污染了的接口,尽量去修改,若变更的风险较大,则采用适配器模式进行转化处理。
贯彻使用接口隔离原则最好的方法就是一个接口一个方法,保证绝对符合接口隔离原则(有可能不符合单一职责原则),但你会采用吗?!不会,除非你是疯子!那怎么才能正确的使用接口隔离原则呢? 答案是根据经验和常识决定接口的粒度大小,接口粒度太小,导致接口数据剧增,开发人员呛死在接口的海洋里;接口粒度太大,灵活性降低,无法提供定制服务,给整体项目带来无法预计的风险。怎么准确的实践接口隔离原则?一句话:实践,经验和领悟!