ISP声明:
客户不应被迫依赖于他们不使用的方法。
ISP涉及到重要特征- 内聚性 和 耦合度。
理想情况下,您的组件必须高度定制化。这会提高代码的健壮性和可维护性。
实施ISP会带来以下好处:
如果您想了解更多关于软件设计原则的信息,请获取《敏捷软件开发:原则、模式与实践》一书。
public interface Reportable {
void printPDF();
void printWord();
void printExcel();
void printPPT();
void printHTML();
}
如果您有一个只能将某些数据导出为Excel格式的客户端,您可以实现接口,但您是否只需要实现excel方法?答案是否定的,即使您不打算使用所有方法,您仍需要编写所有方法的实现,这可能会导致大量的垃圾代码,从而使代码难以维护。
请记住保持简单,不要重复自己,您会发现自己已经在使用这个原则而不知道。
它简化了任何一个客户端使用的界面,并消除了他们可能在不需要的界面部分上开发的依赖。
Robert Martin's paper关于这个主题提供了一个较少提到的解释:
客户端对接口施加的向后力。
如果两个类直接依赖于第三个类的两种不同方法,则增加了更改前两个类中任何一个的可能性会影响另一个类的可能性。
假设我们有三个类:Red
,Green
和Blue
。
Red
和Green
都依赖于Blue
,但每个类依赖于不同的方法。这意味着Red
依赖于Blue
的一种方法,但不使用另一种方法。同样,Green
依赖于Blue
,但仅使用其中一种方法而不是另一种方法。
红色
和 绿色
中,因为每个类都依赖于一个名为 蓝色
的类,但却没有使用它至少一个方法。红色
,并且我还需要更改 蓝色
以适应 红色
的需求。绿色
所依赖的 蓝色
中的具体方法,但是,绿色
仍然依赖于 蓝色
,而我已经更改了 蓝色
,这可能仍会影响到 绿色
。红色
的更改有可能会影响到 蓝色
,因为它们都依赖于同一个类。没有客户端应该被强制依赖于其未使用的方法。
从Robert Martin的论文中可以看出,许多ISP的解释实际上是在谈论其他原则。
如果我们在Google中搜索“接口隔离”,大多数包含代码示例的前几个结果都展示了未完全实现接口的类,这并不是ISP的重点。有些甚至错误地重新阐述了原则:
接口隔离原则表明,客户端不应被强制实现他们不使用的接口
...但那不是原则。定义性文件提到这些问题是违反ISP的副作用,但表明它们是Liskov替换违规。
此外,每次向基类添加新接口时,必须在派生类中实现(或允许默认)。事实上,相关做法是将这些接口添加到基类作为nil虚函数而不是纯虚函数;具体是为了避免派生类负担实现它们的需要。正如我们在本专栏的第二篇文章中所学到的,这种做法违反了Liskov替换原则(LSP),导致维护和重用问题。
更重要的是,说客户端不应该实现它不使用的方法甚至没有意义。一个接口的客户端不会实现它使用或不使用的方法-它们消费其方法。 List<E>
的客户端不会实现 List<E>
的方法和属性。它调用 List<E>
的方法和属性。
一个原因是每个接口仅有少量方法,这样拥有多个接口会更容易实现每个接口并正确实现。一个大的接口可能会很难处理。此外,在某个场景中使用一个专注的接口可以使代码更易于维护,因为您可以看到对象的哪个方面正在被使用(例如,IComparable接口让您知道该对象仅在给定场景中用于比较)。
使代码更易读和可管理。
促进类的单一职责(高内聚)。当然,一个类为什么要有没有行为影响的方法?为什么不直接删除它?这就是ISP的作用。
ISP很重要。
ISP的基本思想是:客户端不应被迫依赖于它不使用的方法。
这个原则似乎更加合理。理想情况下,客户端不应实现它不使用的方法。
请参阅下面的SE问题以获取代码示例:
优点:
灵活性:在没有ISP的情况下,你有一个通用FAT接口和许多实现它的类。假设你有1个接口和50个类。如果接口发生变化,所有50个类都必须更改它们的实现。
采用ISP后,您将把通用FAT接口分成细粒度的小接口。如果小细粒度接口发生变化,则只会影响实现该接口的类。
可维护性和易用性:由于更改仅限于细粒度接口而不是通用FAT接口,因此代码维护更容易。不相关的代码不再是实现类的一部分。