我相信我们发明一些东西是出于某些原因:OOP 的出现是因为过程式编程不能满足我们的需求;接口也是如此,因为抽象等其他 OOP 特性不能满足我们的需求。
有很多关于接口是什么、可以做什么以及如何使用它的文章和指南,但是,我想知道创建接口背后的实际哲学是什么?为什么我们需要有接口?
从概念上讲,接口是一个契约。这是一种说法,任何实现这个接口的东西都能够做这些事情。
不同的语言有不同的接口可以定义的东西,以及定义它们的不同方式,但这个概念仍然存在。
使用接口可以让您不必关心某些特定任务是如何完成的;它允许您确保它已完成。
通过允许实现不同,并允许代码只定义它所需要的最小子集,它允许您概括您的代码。
也许您想编写一个方法来在屏幕上写入一系列数字。您不想在任何(许多)其他常用数据结构上为数组、集合、树编写方法来执行此操作。您不需要关心您是在处理数组还是链表,您只需要某种获取项目序列的方法。接口允许您只定义所需的最小集合,比如说一个getNextItem
方法,然后如果所有这些数据结构都实现了该方法和接口,它们可以使用一个通用方法。这比为您要使用的每种数据结构类型编写单独的方法要容易得多。(这不是接口的唯一用途,只是一个常见的用途。)
在 Java 中,类只能从一个类继承,但它们可以实现多个接口。接口类似于抽象类,但是如果一个类扩展了一个抽象类,那么该类就不能扩展任何其他类。接口解决了这个问题,你可以让一个类扩展一个抽象类并实现许多接口。
我完全同意 susomena 的观点,但这并不是您在使用接口时获得的唯一好处。
例如。在我们当前的应用程序中,模拟在单元测试方面起着重要作用。单元测试的理念是,你真的应该只测试这个单元本身的代码。但是,有时还有其他依赖项,需要获取“被测单元”(SUT)。也许这个依赖还有其他的依赖等等。因此,您无需复杂地构建和配置依赖关系树,而是伪造这个特定的依赖关系。许多模拟框架需要使用类的接口设置,SUT 依赖于该接口。通常可以模拟具体类,但在我们的例子中,模拟具体类会导致单元测试的奇怪行为,因为构造函数调用。但是模拟接口没有,因为接口没有构造函数。
我个人选择抽象类实现的理念是构建分层类结构,其中需要抽象基类的一些默认行为。如果没有任何默认行为,则派生类应该继承,我看不出不选择接口而不是抽象类实现的任何要点。
这里还有另一个(不太好)的例子,说明如何选择一种技术而不是另一种技术。想象一下,你有很多动物课程,比如Cat
和Dog
。抽象类Animal
可能会实现这个默认方法:
public abstract void Feed()
{
Console.WriteLine("Feeding with meat");
}
没关系,如果你有很多动物,就可以吃肉。对于少数不喜欢肉的动物,您只需要重新实现Feed()
.
但是,如果这些动物有点像美食家呢?要求是,每只动物都有自己喜欢的食物吗?我宁愿在那里选择一个接口,所以程序员被迫Feed()
为每种单一类型的IAnimal
.
IMO 描述接口的最佳文本是Robert Martin 的 ISP。
接口的真正威力来自以下事实:(1)您可以将一个对象视为具有许多不同的类型(因为一个类可以实现不同的接口)以及(2)将来自不同层次树的对象视为具有相同的类型(因为不相关的类可以实现相同的接口)。
如果您有一个带有某种接口类型参数的方法(例如,Comparable),这意味着该方法可以接受实现该接口的任何对象“忽略”类(例如,字符串或整数,两个不相关的类)实现 Comparable)。
因此,接口是比抽象类更强大的抽象。
接口被引入 OOP 是因为它在the producer consumer paradigm
. 让我用一个例子来解释一下……
假设有一家供应商向所有大汽车公司供应轮胎。汽车公司被认为是消费者,轮胎供应商是生产者。现在消费者向生产商指示必须生产轮胎的各种规格(例如直径、轴距等);生产商必须严格遵守所有这些规范。
让我们以此类比 OOP... 让我们开发一个应用程序来实现一个堆栈,您正在为此开发 UI;让我们假设您正在使用堆栈库(作为 .dll 或 .class)来实际实现堆栈。在这里, 您是消费者,而实际编写堆栈程序的人是生产者。现在,您指定堆栈的各种规范,说明它应该具有推送元素和弹出元素的规定以及窥视的规定在当前堆栈指针处。您还可以通过指定返回类型和参数(函数的原型)来指定访问这些规定的接口,以便您知道如何在应用程序中使用它们。
实现这一点的最简单方法是创建一个接口并要求生产者实现该接口。这样,无论生产者使用什么逻辑(只要满足您的需求,您就不会为实现而烦恼),他将实现具有精确返回类型和参数的 push、pop 和 peep 方法。
换句话说,你让生产者严格遵守你的规范和通过让他实现你的接口来满足你的需求的方式。如果他不实现您的接口,您将不会接受任何供应商的堆栈;因为您无法确定它是否适合您的确切需求。
class CStack implements StackInterface
{//this class produced by the producer must have all three method implementation
//interface defined by the consumer as per his needs
bool push(int a){
...
}
int pop(){
....
}
int peep(){
...
}
}