在面向对象编程中,接口(Interface)和抽象类(Abstract Class)是两种重要的语言构造,它们帮助实现代码的模块化和可扩展性。然而,并非所有的场景都需要使用它们。本文将探讨何时以及为什么需要使用接口和抽象类,并提供一些编程实践中的建议。
最近,与某人讨论了将第三方库调用抽象化到一个类中的想法,以便调用应用程序仅依赖于这个类。这样做的原因是,希望有一个单一的点与第三方库通信,而不是让对库的调用遍布整个应用程序代码。未来,如果决定使用另一个第三方库,只需要更改这个类的内部实现,调用者将不受影响。
这种解决方案的设计方式是,新的实现包含了一个接口,一个实现此接口的抽象类,以及一个包含实际库调用的具体类。在正常情况下,会说这是一个优雅的解决方案,因为遵循了依赖倒置原则,并正确使用了接口。但这次,它让思考否真的需要接口和抽象类。
接口主要用于定义一个契约。调用者可以使用接口,并选择任何满足此契约的具体实现,无论是通过注入具体实现,还是使用工厂或服务定位器。重要的是理解“调用者可以选择”是这里的真正关键。现在想象一个场景,比如验证电子邮件格式。会有任何场景需要调用者选择提供此功能的类吗?如果答案是“不”,那么为什么需要接口?为什么调用者不能直接使用这个类呢?
那么,如何决定是否应该创建接口呢?答案是,当想要定义一个可以由多个具体类实现的契约,并且这些多个具体类可以在应用程序中共存,并且调用者将根据某些条件选择性地调用它们时。如果有一个场景,根本不可能有多个具体实现共存于应用程序中,那么创建接口可能是不必要的,如果这样做,可能是过度设计。
这里的关键是,不应该仅仅为了创建接口而创建接口。创建接口应该有正当的理由。同时,在选择创建接口时,应该考虑可扩展性和灵活性。但如果相当确定不需要强制执行契约,并且不可能有多个具体实现共存,那么可能根本不应该创建接口,而应该直接使用具体类。
首先,抽象(Abstraction)和抽象类(Abstract Class)是不同的。抽象是类为其调用者提供的,而抽象类是一种语言构造。这里的问题是,应该在什么时候创建一个抽象类。答案应该是——每当有可能有多个具体实现共存,并且这些具体实现需要一些默认和/或共同行为时,应该创建一个抽象类,并将这些默认/共同行为放入这个抽象类中。这个抽象类现在将实现接口,具体类将继承这个抽象类。
使用面向对象的原则和实践对于创建好的软件至关重要。但应该首先理解这些实践背后的理由,以及这些模式试图解决的问题。只有这样,才能在应用程序中开始使用它们。否则,如果在没有需求的情况下开始使用这些实践,最终的设计/代码可能是过度的。
应该记住KISS(保持简单,愚蠢)的原则,并且同时分析应用程序的需求,然后相应地开始设计解决方案。这不仅适用于接口和抽象类,也适用于设计和架构模式。不应该仅仅为了使用它们而将模式包含在设计中,而应该有正当的理由使用它们,然后才应该使用它们。