设计模式:接口隔离原则
定义
接口隔离原则(Interface Segregation Principle, ISP),定义为:
Clients should not be forced to depend upon interfaces that they don’t use. (客户端不应该依赖它不需要的接口。)
The dependency of one class to another one should depend on the smallest possible interface. (类间的依赖关系应该建立在最小的接口上。)
接口隔离原则是对接口的使用进行约束规范的一个原则,它告诉我们要想把接口用好,关键在于隔离。隔离,指断绝接触、断绝往来。那么我们使用接口时,要隔离什么东西呢?对于上述定义的第1点,“客户端不应该依赖它不需要的接口”,这里的隔离是指客户端和它不需要的接口隔离,也就是客户端不要使用它不需要的接口,这个很容易理解,在实践中也很容易实现。我们着重看一下第2点,“类间的依赖关系应该建立在最小的接口上”,它要求“最小的接口”,也就是该接口中没有多余的方法,所以这里的隔离是指和多余的方法隔离。
综上所述,接口隔离原则告诉我们,不要把一大堆方法塞进一个接口里,导致这个接口变得臃肿无比。应该要根据实际需要,让接口中只有用得上的方法,也就是说要细化我们的接口。
好处
现在我们知道,接口隔离原则的要点,就是要细化我们的接口。那么这样做具体有什么好处呢?主要有四个好处,分别是:1.避免接口污染;2.提高灵活性;3.提供定制服务;4.实现高内聚。下面就来详细说一下接口隔离原则的好处。
避免接口污染
一个类如果要实现一个接口,那么就要实现这个接口要求的所有方法,如果这个接口里面包含这个类不需要的方法,那么就会造成接口污染,这是不好的设计,会对系统留下隐患。
比如说我们有一个枪的接口,枪有两个属性:扳机和子弹。枪有一个功能:射杀。其接口如下:
@protocol IGun <NSObject>
@property (strong,nonatomic) id trigger;//扳机
@property (strong,nonatomic) id bullet;//子弹
- (void)shot;//射杀
@end
然后我们现在需要一个玩具枪的类。玩具枪有扳机也有子弹,只是不能射杀。为了图方便,我们直接用IGun这个接口来实现我们的玩具枪:
#import "IGun.h"
@interface ToyGun : NSObject<IGun>
@end
@implementation ToyGun
@synthesize trigger = _trigger,bullet = _bullet;
- (void)shot{
//空实现,什么也不做
}
@end
玩具枪是不能射杀的,但是由于它实现了IGun
这个接口,所以只能空实现它并不需要的shot
方法,于是玩具枪这个类就被污染了。这好像也没有什么不妥,但是这是有隐患的,因为玩具枪一旦实现了IGun
接口,那么在程序里它就代表一把能射杀的枪。假设在后面突然遇到了一个老虎,唯一保命的方法就是拿枪射杀这个老虎,结果你拿到的是你之前为了图方便做的ToyGun
,那么你面临的就是灭顶之灾。
提高灵活性
一个类是可以同时实现多个接口的,所以将一个臃肿的接口分割为若干个小接口,通过小接口的不同组合可以满足更多的需求。
举个例子。我们现在需要一个代表美女的接口,美女的标准也很明确:面貌、身材和气质,那么我们的美女接口就出来了:
@protocol IPrettyGirl <NSObject>
- (void)goodLooking;//好面貌
- (void)niceFigure;//好身材
- (void)greatTemperament;//好气质
@end
这并没有什么问题。但是在现实中,一定是要美貌和气质兼备的才算美女吗?非也,其实也有长得不好看,但是气质很好的气质美女的,当然也有没有气质但是长得好看的美女。这样上面的接口就不适用了,因为按照上面的的接口,只有长得好看而且气质好的才算美女。
可以通过细化这个接口解决这个问题。上述的接口可以一分为二:
只有外貌好的美女:
@protocol IGoodBodyGirl <NSObject>
- (void)goodLooking;//好面貌
- (void)niceFigure;//好身材
@end
只有气质好的美女:
@protocol IGreatTemperamentGirl <NSObject>
- (void)greatTemperament;//好气质
@end
然后通过这两个接口的不同组合,就能满足外貌美女、气质美女和外貌气质俱佳的美女的不同需求了。所以,细化接口可以让我们的接口更加灵活,满足更多需求。
提供定制服务
什么是定制服务?定制服务就是单独为一个个体提供优良的服务。我们在做系统设计时也需要考虑对系统之间或模块之间的接口提供定制服务。提供定制服务就必然有一个需求:只提供访问者需要的方法。这也是可以通过细化接口实现的。
比如我们开发了一个图书管理系统,其中有一个查询图书的接口:
@protocol IBookSearcher <NSObject>
- (void)searchByAuthor;//根据作者搜索
- (void)searchByTitle;//根据书名搜索
- (void)searchByCatagory;//根据分类搜索
- (void)complexSearch;//复杂的搜索
@end
我们的图书馆管理系统的访问者有管理人员和公网,其中complexSearch
方法非常损耗服务器的性能,它只提供给管理人员使用。其他方法管理人员和公网都可以使用。公网这部分是另一个项目组在开发的,所以当时我们口头上跟公网项目组说明不能在公网上调用complexSearch
这个方法。图书馆管理系统上线后,有一天发现系统速度非常慢,在熬了一个通宵排查后,发现是由于公网项目组某个程序员的疏忽,把complexSearch
方法公布到了公网中...
显然通过口头的方式说哪一个方法不能调用是不管用的,要想彻底解决这个问题,还是得通过细化接口,为访问者定制专有的接口才行。那么上述的接口可以一分为二:
简单的搜索:
@protocol ISimpleBookSearcher <NSObject>
- (void)searchByAuthor;//根据作者搜索
- (void)searchByTitle;//根据书名搜索
- (void)searchByCatagory;//根据分类搜索
@end
复杂的搜索:
@protocol IComplexBookSearcher <NSObject>
- (void)complexSearch;//复杂的搜索
@end
这样我们就可以分别给管理人员和公网定制接口了:
给管理人员提供
ISimpleBookSearcher
和IComplexBookSearcher
两个接口;给外网提供
ISimpleBookSearcher
这个接口。
所谓的定制服务,就是通过细化接口,实现给不同的客户提供不同的接口的目的。定制服务可以有效避免因为给客户提供了多余的方法而造成的风险。
高内聚
什么是高内聚?高内聚就是提高接口、类、模块的处理能力,减少对外的交互。比如说,你告诉你的下属“一个小时之内去月球搬一块石头回来”,然后你就躺在海滩上晒着太阳喝着果汁,一个小时之后你的下属就搬着一块月亮上的石头回来给你了。这种不讲任何条件,不需要你关心任何细节,立即完成任务的行为就是高内聚的表现。
具体到接口中,还是尽量细化你的接口。接口是对外界的承诺,承诺越少对系统的开发越有利,变更的风险也就越少,同时也有利于降低成本。
注意
接口隔离原则和单一职责原则非常类似。单一职责原则要求接口的职责是单一的,而接口隔离原则要求接口尽量细化,它们有异曲同工之妙,都是要让我们的接口功能尽量单一,尽量小。
但是,单一职责原则的着重点是在“职责”,而接口隔离原则只单纯地要求接口最小化。那么,如果已经满足单一职责原则的接口,在当前的需求下还可以继续细化,那么还需要细化吗?答案是不要再细化了。在实践中,接口设计的粒度越小,系统就越灵活,这是事实。但是灵活的同时也带来了系统的复杂化,导致开发难度增加。所以接口并不是越小越好,必须要有一个度。当单一职责原则和接口隔离原则存在矛盾时,以满足单一职责原则为底线。