现在广为流行的ISP的表述一般是这样的:使用多个专门的接口比使用单一的总接口要好;从一个客户类的角度来讲,一个类对另外一个类的依赖性应当是建立在最小的接口上的。这都是一个意思。
我们下面这个图表示的关系:
【注】客户类只列出了它确实实现的方法。实际上,很多语言都要求继承关系中,子类要实现父类所有的虚方法,除非这个方法在父类中以及提供了默认实现方式。
接口BaseInterface定义了5个方法,有三个客户依赖于这个接口。其中,ConsumerA只依赖于BaseInterface的A1()和A2()方法;ConsumerB只依赖于BaseInterface的B1()和B2()方法;ConsumerC只依赖于BaseInterface的C1()。这是一个典型的违背ISP原则的设计。这样的设计有什么问题呢?假设A1()方法的定义需要改变,或者ConsumerA要求增加A3()方法,出现什么结果呢?很显然,因为这个改变需要修改BaseInterface,那么所有依赖于BaseInterface的客户(ConsumerA/B/C)都要受到影响(需要重新编译、部署、测试等等)!ConsmerB和ConsumerC岂不是很冤枉?
我们可以检讨一下:这样的接口是怎么设计出来的呢?一种情况是设计的时候根本没考虑接口的功能或职责问题,一股脑的把所有可能的方法统统罗列;另一种更常见的情况是:开始的时候,只有一个客户ConsumerA,需要两个方法A1()。A2()于是,根据面向抽象的要求,抽象出来一个接口BaseInferface,还有这两个方法。后来,又有新的客户ConsumerB,提出了新的方法要求B1()、B2(),于是这两个也被加入BaseInferface中。这个过程持续下去,随着一步步积累,BaseInterface变得越来越臃肿。
正确的设计应该是把接口进行拆分。拆分的原则可以参照SRP的要求,按照职责和功能对方法进行分组。每一组方法抽象成一个接口,每一个客户都只依赖于特定的接口,如下图:
这时候,如果InterfaceA进行了修改,添加了新方法A3(),只需要修改ConsumerA就可以了,其他客户根本就不受影响!