接口隔离原则(Interface Segregation Principle,ISP)。ISP定义:
客户端不应该依赖它不需要的接口;一个类对另一个类的依赖应该建立在最小的接口上。
案例说明
直观认识ISP原则,直接上图:
在图中所描述的应用中,有多个用户需要操作Ops类。现在,我们假设User1类只需要使用op1,User2类只需要使用op2,User3类只需要使用op3。
在这种情况下,如果使用Java语言来实现,User1并不需要调用op2、op3方法,但是在代码层次上且与Ops类形成了依赖。这种依赖意味着我们对Ops类的op2方法所做的任何修改,即使不会影响User1的功能,也会导致Ops需要被重新编译和部署。
这个问题可以通过将不同的操作隔离成接口来解决,如下图:
同样,如果使用Java语言来实现,那么User1类会依赖于U1Ops接口,且U1Ops接口只有op1方法,但是User1类不会依赖Ops类,Ops类只需要负责实现U1Ops接口的方法即可。这样一来,我们之后对Ops类做的修改只要不影响到User1的功能,就不需要重新编译和部署User1了。
ISP于编程语言
我们知道,一个算法、一个设计思路是跟具体的编程语言无关的。上面的例子很大程度上依赖于编程语言的一个特性,那就是导入需要的依赖代码,如Java语言的import,C语言的include等。而正是这些语句带来了代码之间的依赖关系,这也导致了某些模块需要被重新编译和重新部署。
对于Python和Ruby这样的动态语言来说,它们属于解释性语言,它们所依赖的代码会在运行时被推演出来,所以也就不存在重新编译和重新部署的必要性。这也是动态语言比静态语言更灵活、耦合度更松的原因。
当然,如果是这样的话,我们可能会误以为ISP只是一个与特定编程语言相关的设计原则,而非软件架构的设计原则,这就错了。
ISP于软件架构
在一般情况下,任何层次的软件设计如果依赖于不需要的东西,都会是有害的。从代码层次来说,这样的依赖会导致不必要的重新编译和重新部署,从软件架构层次来说,问题也是类似的。
例如,我们在设计系统S(System)时,想要在系统中引入某个框架F(Framework),并假设框架F又绑定在一个特定的数据库D(DB)上,这样就形成了S依赖于F,F依赖于D的关系。如图:
在这种情况下,如果D中包含了F不需要的功能,那么这些功能同样也会是S不需要的。而我们对D中这些功能的修改将会导致F需要被重新部署,后者又会导致S的重新部署。更糟糕的是,D中一个无关功能的错误也可能会导致F和S运行出错。
总结
ISP设计原则告诉我们:任何层次(代码和架构)的软件设计如果依赖了它并不需要的东西,就会带来意料之外的麻烦。