“编程接口”意味着什么?
我已经看到过这几次,我不清楚它的含义。 何时以及为何要这样做?
我知道接口是做什么的,但是我不清楚这一点,这让我觉得我错过了正确使用它们。
如果你这样做就是这样吗?
IInterface classRef = new ObjectWhatever()
你可以使用任何实现IInterface
类? 你什么时候需要这样做? 我能想到的唯一的事情就是如果你有一个方法,并且你不确定什么对象会被传递期望实现IInterface
。 我想不出你多久需要这么做......(另外,你怎么能写一个接受实现接口的对象的方法?这可能吗?)
对不起,如果我完全错过了这一点。
这里有一些关于这些问题的精彩答案,这些问题涉及各种关于接口和松散耦合代码,控制反转等等的大量细节。 有一些相当热闹的讨论,所以我想借此机会分解一些东西,以便理解为什么界面很有用。
当我开始接触界面时,我也对它们的相关性感到困惑。 我不明白你为什么需要他们。 如果我们使用像Java或C#这样的语言,我们已经有了继承,并且我将接口视为一种较弱的继承和思想形式,“为什么要麻烦?” 从某种意义上说,我认为接口是一种弱的继承形式,但除此之外,我终于明白了它们作为一种语言结构的用法,把它们当作一种手段来分类共同的特征或行为,潜在的许多非相关的对象类。
例如 - 说你有一个SIM游戏,并有以下类:
class HouseFly inherits Insect {
void FlyAroundYourHead(){}
void LandOnThings(){}
}
class Telemarketer inherits Person {
void CallDuringDinner(){}
void ContinueTalkingWhenYouSayNo(){}
}
显然,这两个对象在直接继承方面没有共同之处。 但是,你可以说他们都很烦人。
假设我们的游戏需要有某种随机的事情,让他们吃晚饭时会让玩家感到不舒服。 这可能是一个HouseFly
或一个Telemarketer
HouseFly
或两者兼而有之 - 但你如何让一个功能都可以兼顾? 你如何以同样的方式询问每种不同类型的对象“做他们讨厌的事情”?
要认识到的关键是, Telemarketer
HouseFly
和HouseFly
都有共同的松散解释行为,尽管他们在建模方面没有任何相似之处。 所以,我们来创建一个可以实现的接口:
interface IPest {
void BeAnnoying();
}
class HouseFly inherits Insect implements IPest {
void FlyAroundYourHead(){}
void LandOnThings(){}
void BeAnnoying() {
FlyAroundYourHead();
LandOnThings();
}
}
class Telemarketer inherits Person implements IPest {
void CallDuringDinner(){}
void ContinueTalkingWhenYouSayNo(){}
void BeAnnoying() {
CallDuringDinner();
ContinueTalkingWhenYouSayNo();
}
}
我们现在有两个班,每个班都可以用自己的方式烦人。 而且他们不需要从相同的基类中派生出来并且拥有共同的固有特征 - 他们只需要满足IPest
的合同 - 合约很简单。 你只需要BeAnnoying
。 在这方面,我们可以模拟以下内容:
class DiningRoom {
DiningRoom(Person[] diningPeople, IPest[] pests) { ... }
void ServeDinner() {
when diningPeople are eating,
foreach pest in pests
pest.BeAnnoying();
}
}
在这里,我们有一个餐厅,可以接受一些食客和一些害虫 - 注意使用界面。 这意味着在我们的小世界里, pests
数组的成员实际上可以是Telemarketer
HouseFly
对象或HouseFly
对象。
ServeDinner
方法在服务晚餐时被调用,我们餐厅的人员应该吃饭。 在我们的小游戏中,当我们的有害生物发挥作用时 - 每种有害生物都被指示为通过IPest
界面感到烦恼。 通过这种方式,我们很容易让Telemarketers
HouseFlys
和HouseFlys
以各自的方式烦人 - 我们只关心在DiningRoom
对象中有什么东西是有害生物,我们并不在乎它是什么,他们可以与其他人没有共同之处。
这个非常人为的伪代码示例(拖延时间比我预期的要长得多)仅仅是为了说明最终为我提供了关于何时使用接口的信息。 我为这个例子的愚蠢行为提前道歉,但希望它能帮助你理解。 而且,可以肯定的是,这里发布的其他答案确实涵盖了当今设计模式和开发方法中使用界面的全部内容。
我过去给学生的具体例子是他们应该写
List myList = new ArrayList(); // programming to the List interface
代替
ArrayList myList = new ArrayList(); // this is bad
这些在一个简短的程序中看起来完全一样,但是如果你继续在你的程序中使用myList
100次,你可以开始看到不同之处。 第一个声明确保您只调用由List
接口定义的myList
上的方法(因此没有ArrayList
特定的方法)。 如果你已经用这种方式编程接口,那么以后你可以决定你确实需要
List myList = new TreeList();
你只需要在那一个地方改变你的代码。 您已经知道,由于您已编程到界面,因此您的其他代码不会执行任何因更改实施而被破坏的内容。
当你谈论方法参数和返回值时,好处更加明显(我认为)。 以此为例:
public ArrayList doSomething(HashMap map);
该方法声明将您与两个具体实现( ArrayList
和HashMap
)联系起来。 只要从其他代码调用该方法,对这些类型的任何更改可能意味着您将不得不更改调用代码。 编程到接口会更好。
public List doSomething(Map map);
现在,无论您返回哪种List
或返回哪种Map
作为参数。 您在doSomething
方法中进行的更改不会强制您更改调用代码。
编程接口说:“我需要这个功能,我不在乎它来自哪里。”
考虑(在Java中), List
接口与ArrayList
和LinkedList
具体类。 如果我关心的是,我有一个包含多个数据项的数据结构,我应该通过迭代访问,我会选择一个List
(99%的时间)。 如果我知道我需要从列表的任一端进行常量插入/删除,我可能会选择LinkedList
具体实现(或者更可能使用Queue接口)。 如果我知道我需要索引随机访问,我会选择ArrayList
具体类。