4.3 保证接口的纯洁性
4.3 保证接口的纯洁性
接口隔离原则是对接口进行规范约束,其包含以下4层含义:
接口要尽量小
这是接口隔离原则的核心定义,不出现臃肿的接口(Fat Interface),但是“小”是有限度的,首先就是不能违反单一职责原则,什么意思呢?我们在单一职责原则中提到一个IPhone 的例子,在这里,我们使用单一职责原则把两个职责分解到两个接口中,类图如图4-3所示。
仔细分析一下IConnectionManager接口是否还可以再继续拆分下去,挂电话有两种方式:一种是正常的电话挂断,一种是电话异常挂机,比如突然没电了,通信当然就断了。这两种方式的处理应该是不同的,为什么呢?正常挂电话,对方接受到挂机信号,计费系统也就停止计费了,那手机没电了这种方式就不同了,它是信号丢失了,中继服务器检查到了, 然后通知计费系统停止计费,否则你的费用不是要疯狂地增长了吗?
思考到这里,我们是不是就要动手把IConnectionManager接口拆封成两个,一个接口是负责连接,一个接口是负责挂电话?是要这样做吗?且慢,让我们再思考一下,如果拆分了,那就不符合单一职责原则了,因为从业务逻辑上来讲,通信的建立和关闭已经是最小的业务单位了,再细分下去就是对业务或是协议(其他业务逻辑)的拆分了。想想看,一个电话要关心3G协议,要考虑中继服务器,等等,这个电话还怎么设计得出来呢?从业务层次来看,这样的设计就是一个失败的设计。一个原则要拆,一个原则又不要拆,那该怎么办? 好办,根据接口隔离原则拆分接口时,首先必须满足单一职责原则。
接口要高内聚
什么是高内聚?高内聚就是提高接口、类、模块的处理能力,减少对外的交互。比如你告诉下属“到奥巴马的办公室偷一个×××文件”,然后听到下属用坚定的口吻回答你:“是,保证完成任务!”一个月后,你的下属还真的把×××文件放到你的办公桌上了,这种不讲任何条件、立刻完成任务的行为就是高内聚的表现。具体到接口隔离原则就是,要求在接口中尽量少公布public方法,接口是对外的承诺,承诺越少对系统的开发越有利,变更的风险也就越少,同时也有利于降低成本。
定制服务
一个系统或系统内的模块之间必然会有耦合,有耦合就要有相互访问的接口(并不一定就是Java中定义的Interface,也可能是一个类或单纯的数据交换),我们设计时就需要为各个访问者(即客户端)定制服务,什么是定制服务?定制服务就是单独为一个个体提供优良的服务。我们在做系统设计时也需要考虑对系统之间或模块之间的接口采用定制服务。采用定制服务就必然有一个要求:只提供访问者需要的方法,这是什么意思?我们举个例子来说明,比如我们开发了一个图书管理系统,其中有一个查询接口,方便管理员查询图书,其类图如图4-4所示。
在接口中定义了多个查询方法,分别可以按照作者、标题、出版社、分类进行查询,最后还提供了混合查询方式。程序写好了,投产上线了,突然有一天发现系统速度非常慢,然后就开始痛苦地分析,最终发现是访问接口中的complexSearch(Map map)方法并发量太大, 导致应用服务器性能下降,然后继续跟踪下去发现这些查询都是从公网上发起的,进一步分析,找到问题:提供给公网(公网项目是另外一个项目组开发的)的查询接口和提供给系统内管理人员的接口是相同的,都是IBookSearcher接口,但是权限不同,系统管理人员可以通过接口的complexSearch方法查询到所有的书籍,而公网的这个方法是被限制的,不返回任何值,在设计时通过口头约束,这个方法是不可被调用的,但是由于公网项目组的疏忽,这个方法还是公布了出去,虽然不能返回结果,但是还是引起了应用服务器的性能巨慢的情况发生,这就是一个臃肿接口引起性能故障的案例。
问题找到了,就需要把这个接口进行重构,将IBookSearcher拆分为两个接口,分别为两个模块提供定制服务,修改后的类图如图4-5所示。
提供给管理人员的实现类同时实现了ISimpleBookSearcher和IComplexBookSearcher两个接口,原有程序不用做任何改变,而提供给公网的接口变为ISimpleBookSearcher,只允许进行简单的查询,单独为其定制服务,减少可能引起的风险。
接口设计是有限度的
接口的设计粒度越小,系统越灵活,这是不争的事实。但是,灵活的同时也带来了结构的复杂化,开发难度增加,可维护性降低,这不是一个项目或产品所期望看到的,所以接口设计一定要注意适度,这个“度”如何来判断呢?根据经验和常识判断,没有一个固化或可测量的标准。