我为什么要使用接口?[关闭]


74

我了解它们会迫使您实现方法等,但我无法理解的是为什么要使用它们。谁能给我一个很好的例子或解释我为什么要实现这一点。


3
人民,他没有要求定义。

2
我在整个接口与继承方面苦苦挣扎了很长时间。例如,继承一个抽象类和实现一个接口有什么区别?直到我读过《 Head First-Design Patterns》,再也没有老师对我说过。它是轻量级的,而不是那么昂贵。
鲍里斯·卡伦斯

3
从技术上讲,接口是仅包含抽象方法的类。因此Java实际上具有多重继承(仅允许用于接口)。其原因是多重继承的问题主要与实例变量有关。(部分方法实现)
雨果

这里有一个类似问题的更多答案:接口与基类
razlebe

Answers:


64

一个特定的示例:接口是指定其他人的代码必须满足的合同的好方法。

如果我正在编写代码库,则可能会编写对具有某些行为的对象有效的代码。最好的解决方案是在接口(没有实现,仅是描述)中指定这些行为,然后在我的库代码中使用对实现该接口的对象的引用。

然后,任何随机的人都可以出现,创建实现该接口的类,实例化该类的对象,并将其传递给我的库代码,并期望它能工作。注意:当然有可能在不考虑接口意图的情况下严格实现接口,因此仅实现接口并不能保证一切正常。愚蠢总能找到办法!:-)

另一个具体示例:两个团队在必须合作的不同组件上工作。如果两个团队在第一天坐下来并就一组接口达成一致,那么他们可以走自己的路,并围绕这些接口实现其组件。团队A可以构建测试工具,以模拟团队B中的组件进行测试,反之亦然。并行开发,错误更少。

关键是接口提供了抽象层,因此您可以编写忽略不必要细节的代码。

大多数教科书中使用的规范示例是排序例程。您可以对任何类别的对象进行排序,只要您能够比较任何两个对象即可。因此,您可以通过实现IComparable接口使任何类可排序,这迫使您实现用于比较两个实例的方法。编写所有排序例程都是为了处理对IComparable对象的引用,因此,一旦实现IComparable,就可以在类的对象集合上使用这些排序例程中的任何一个。


接口是“最佳”解决方案?那继承呢?使用接口,您必须实现使用该接口的每个类中的每个成员,即使这些实现相同。通过继承,基类可以实现公共成员,而派生类仅包含自定义成员
DOK

3
继承往往很脆弱。如果您在扩展超类时违反了隐式的“是”关系,那么您在寻求麻烦。
罗伯特·范·胡斯

Revah:好的,您需要正确使用继承。但是,与在基类中使用方法的单个版本(如果适用)相比,您必须使用接口编写(并维护)更多重复的代码。
DOK

2
@DOK:您假设实际上有可能在抽象基类中编写实现。对于我给出的示例,这甚至是不可能的-您必须使用接口。
斯图尔特·约翰逊

1
@DOK,如果每个实现接口的类在其实现中都具有相同的代码,则可以,您可能希望继承。接口的重点是实现可能会不同,但合同应该相同。
hmcclungiii

10

接口定义合同,这就是关键词。

当需要在程序中定义合同时,可以使用接口,但实际上并不关心满足该合同的类的其余所有属性。

因此,让我们看一个例子。假设您有一个提供对列表进行排序的功能的方法。第一件事..什么是清单?您是否真的在意列表排序所包含的元素?您的答案应该是否定的。例如,在.NET中,您有一个名为IList的接口,该接口定义了列表必须支持的操作,因此您无需关心表面之下的实际细节。

回到示例中,您实际上并不知道列表中对象的类……也不在乎。如果您只能比较对象,则可以对它们进行排序。所以你宣布一份合同:

interface IComparable
{
  // Return -1 if this is less than CompareWith
  // Return 0 if object are equal
  // Return 1 if CompareWith is less than this
  int Compare(object CompareWith);
}

该合同规定必须实现接受对象并返回int的方法,以使其具有可比性。现在,您已经定义了合同,并且现在您不再关心对象本身,而是合同,因此您可以执行以下操作:

IComparable comp1 = list.GetItem(i) as IComparable;

if (comp1.Compare(list.GetItem(i+1)) < 0)
  swapItem(list,i, i+1)

PS:我知道这些示例有些天真,但它们只是示例...


一尼特,合同IComparable没有规定你回来10-1但你回来>00<0。这样就可以进行优化,例如只需对int进行排序return this.intNum - CompareWith.intNum。您不必关心它是大于或小于0,而不必关心它是大于还是小于。
Scott Chamberlain

9

一个典型的例子是插件架构。开发人员A编写了主应用程序,并希望确保开发人员B,C和D编写的所有插件都符合其应用程序对它们的期望。


9

理解接口的最简单方法是,它们允许不同的对象公开COMMON功能。这样,程序员就可以编写简单得多,更短的代码,以便对接口进行编程,然后只要对象实现该接口,它将可以正常工作。

示例1: 有许多不同的数据库提供程序,例如MySQL,MSSQL,Oracle等。但是,所有数据库对象都可以做相同的事情,因此您会发现许多数据库对象接口。如果对象实现IDBConnection,则它将公开方法Open()和Close()。因此,如果我希望我的程序与数据库提供程序无关,那么我将对接口进行编程,而不是对特定提供程序进行编程。

IDbConnection connection = GetDatabaseConnectionFromConfig()
connection.Open()
// do stuff
connection.Close()

通过编程到接口(IDbconnection)来查看,现在我可以在配置中交换掉任何数据提供程序,但是我的代码保持完全相同。这种灵活性非常有用并且易于维护。不利的一面是,我只能执行“通用”数据库操作,而不能充分利用每个特定提供程序所提供的优势,因此在进行编程时,您需要权衡取舍,并且必须确定哪种方案对您最有利。

范例2: 如果您发现几乎所有集合都实现了称为IEnumerable的接口。IEnumerable返回一个IEnumerator,该IEnumerator具有MoveNext(),Current和Reset()。这使C#可以轻松地在您的集合中移动。之所以这样做,是因为它公开了IEnumerable接口,它知道对象公开了它需要通过它的方法。这有两件事。1)foreach循环现在将知道如何枚举集合,并且2)现在可以将功能强大的LINQ表达式应用于集合。同样,接口之所以在这里如此有用的原因是,因为所有集合在COMMON中都有某些内容,因此可以移动它们。每个集合可以通过不同的方式移动(链接列表与数组),但是接口的优点在于实现是隐藏的,与接口的使用者无关。MoveNext()为您提供集合中的下一项,它的执行方式无关紧要。很好,是吗?

示例3: 在设计自己的界面时,您只需要问自己一个问题。这些东西有什么共同点?一旦找到对象共享的所有事物,就可以将这些属性/方法抽象到接口中,以便每个对象都可以从该接口继承。然后,您可以使用一个界面针对多个对象进行编程。

当然,我必须给出我最喜欢的C ++多态示例,即动物示例。所有动物都有某些特征。假设他们可以移动,说话,并且都有名字。因为我刚刚确定了我所有动物的共同点,所以我可以将这些特性抽象到IAnimal接口中。然后,我创建一个Bear对象,一个Owl对象和一个Snake对象,这些对象均实现了此接口。您可以将实现同一接口的不同对象存储在一起的原因是,接口代表IS-A复制关系。熊IS-A是动物,猫头鹰IS-A是动物,所以我可以将它们全部收集为动物。

var animals = new IAnimal[] = {new Bear(), new Owl(), new Snake()} // here I can collect different objects in a single collection because they inherit from the same interface

foreach (IAnimal animal in animals) 
{
    Console.WriteLine(animal.Name)
    animal.Speak() // a bear growls, a owl hoots, and a snake hisses
    animal.Move() // bear runs, owl flys, snake slithers
}

您可以看到,即使这些动物以不同的方式执行每个动作,我也可以在一个统一的模型中对它们进行编程,这只是Interfaces的众多优点之一。

因此,接口最重要的一点是对象的共同点,以便您可以以相同的方式针对不同的对象进行编程。节省时间,创建更灵活的应用程序,隐藏复杂性/实现,对现实世界的对象/情况进行建模以及许多其他好处。

希望这可以帮助。


5

汽车上的踏板实现接口。我来自美国,我们在马路右侧行驶。我们的方向盘在汽车的左侧。从左到右的手动变速踏板为离合器->制动器->油门。当我去爱尔兰时,驾驶方向相反。汽车的方向盘在右侧,它们在道路的左侧行驶...但是踏板,踏板啊,它们实现了相同的界面,所有三个踏板的顺序都相同...因此,即使班级不同并且班级所使用的网络也不相同,我仍然对踏板界面感到满意。我的大脑就像其他每辆车一样,能够在这辆车上呼唤我的肌肉。

想一想我们离不开的众多非编程接口。然后回答您自己的问题。


1
因此,美国汽车和英国汽车类实现了汽车接口,该接口具有用于离合器,制动和加速器的方法以及SteeringWheelOrientation属性?然后,Clutch,Brake和Accelerator方法基本相同,但是必须在实现Car?的每个类中复制它们的代码。
DOK

好吧,我并不是想将类比一直带到编程中。OP说他知道他们只是什么而不是为什么要使用它们。我想展示一个界面在现实世界中的外观。有时,需要超越我们当前的思想世界来掌握一个概念。

5
When you need different classes to share same methods you use Interfaces.

我可以使用继承too.There必须有的抓,为什么我们使用interface.Does它使我的代码更简单,或可以节省一些内存,提高了可读性,易于管理,灵活等特点??包括在你的答案
HIRA塔库尔

4

接口在期望充分利用多态性的面向对象系统中绝对必要。

一个典型的例子是IVehicle,它具有Move()方法。您可能拥有实现IVehicle的Car,Bike和Tank类。他们都可以使用Move(),并且您可以编写不关心它正在处理哪种车辆的代码,就可以使用Move()。

void MoveAVehicle(IVehicle vehicle)
{
    vehicle.Move();
}

即使没有接口也可以正常工作。(鸭子输入。)接口仅使您可以告诉编译器您的意图。
雨果

2
实际上,车辆不必是接口,它可以是基本类。接口更适合于您只关心合同而不是继承的服务或类似服务,因此我认为多态实际上对接口而言并不重要。
雨果

是的,你是对的。但是通常有一个定义超类方法的接口。从某种意义上说,接口是超类,只是没有实现。
Eric Z Beard

为什么车辆应该是基础阶级?车辆唯一的共同点是行驶,没有其他实现上的共同点。在这种情况下,使用基类不会带来任何好处,也有许多缺点。
楔子

@Wedge-基类将允许您提供默认的实现。但是,是的,如果需要,您总是可以在以后的时间实现基类。
Cory House

3

接口是多态性的一种形式。一个例子:

假设您要编写一些日志记录代码。日志记录将去某个地方(可能是文件,或者是运行主代码的设备上的串行端口,或者是套接字,或者像/ dev / null一样被丢弃)。您不知道在哪里:登录代码的用户需要自由确定。实际上,您的日志记录代码无关紧要。它只想要可以写入字节的内容。

因此,您发明了一个称为“可以向其写入字节的内容”的接口。记录代码为此接口提供了一个实例(也许在运行时,也许是在编译时配置的。它仍然是多态的,只是种类不同)。您编写一个或多个实现该接口的类,并且只需更改日志记录代码将使用的哪一个即可轻松更改日志记录的位置。其他人可以通过编写自己的接口实现来更改日志记录的位置,而无需更改代码。这基本上就是多态性的含义-足够了解一个对象以特定方式使用它,同时允许它在不需要了解的所有方面有所变化。界面描述了您需要了解的事情。

C的文件描述符基本上是一个接口,“我可以在其中读写字节”,几乎每种类型的语言都在其标准库中潜藏着这样的接口:流或其他。无类型语言通常具有表示流的非正式类型(可能称为合同)。因此,在实践中,您几乎不必自己真正发明这个特定接口:您可以使用语言所提供的功能。

日志记录和流只是一个示例-只要您可以抽象地描述对象应该做什么,但不想将其绑定到特定的实现/类/任何对象,接口就会发生。


2

这样做有很多原因。使用接口时,将来需要重构/重写代码时就可以使用。您还可以为简单操作提供一种标准化的API。

例如,如果您要编写类似于quicksort的排序算法,则对任何对象列表进行排序所需要做的就是可以成功比较其中两个对象。如果创建一个接口(例如ISortable),那么创建对象的任何人都可以实现ISortable接口,并且他们可以使用您的排序代码。

如果要编写使用数据库存储的代码,并且要写入存储接口,则可以在一行中替换该代码。

接口鼓励松散地耦合代码,以便具有更大的灵活性。


2

想象下面的基本接口,它定义了基本的CRUD机制:

interface Storable {
    function create($data);
    function read($id);
    function update($data, $id);
    function delete($id);
}

从此接口,您可以知道实现它的任何对象都必须具有创建,读取,更新和删除数据的功能。这可以通过数据库连接,CSV文件读取器和XML文件读取器,或任何其他想要使用CRUD操作的机制实现。

因此,您现在可以拥有以下内容:

class Logger {
    Storable storage;

    function Logger(Storable storage) {
        this.storage = storage;
    }

    function writeLogEntry() {
        this.storage.create("I am a log entry");
    }
}

如果您传入数据库连接或操纵磁盘上文件的操作,此记录器将不在乎。它只需要知道可以调用create()就可以了。

那么,由此引起的下一个问题是,如果数据库和CSV文件等都可以存储数据,是否不应该从通用的Storable对象继承它们,从而消除对接口的需求?答案是否定的……不是每个数据库连接都可以实现CRUD操作,并且每个文件读取器都适用相同的要求。

接口定义了对象的功能以及您需要如何使用它……不是对象!


2
“接口定义了对象的功能以及您需要如何使用它……而不是对象!” 这对我来说比在此页面上的其他所有内容都更为明确。
先生

2

我见过的最好的Java代码几乎将所有对象引用定义为接口实例,而不是类实例。这是为灵活性和变更而设计的质量代码的强烈标志。


1

如您所述,当您要强迫某人以某种格式创建界面时,它非常有用。

当数据不是某种格式可能意味着在代码中进行危险的假设时,接口是很好的选择。

例如,目前我正在编写一个将数据从一种格式转换为另一种格式的应用程序。我想强迫他们将这些字段放入其中,以便我知道它们将存在并且有更大的机会被正确实施。我不在乎是否会出现另一个版本,也不会为它们编译,因为无论如何仍然需要数据。

接口很少使用,因为这一点,因为通常你可以假设或并不真正需要的数据,你需要做什么。


关于接口易碎的好地方。您不能在不破坏很多内容的情况下进行更改。
DOK

1

接口仅定义interface。稍后,您可以定义方法(在其他类上),该方法将接口接受为参数(或更准确地说,是实现该接口的对象)。这样,您的方法可以对各种各样的对象进行操作,它们的唯一共同之处在于它们实现了该接口。


1

首先,它们为您提供了额外的抽象层。您可以说“对于此功能,此参数必须是具有这些参数的方法的对象”。您可能还希望以某种抽象的方式设置这些方法的含义,但允许您对代码进行推理。使用鸭式语言,您可以免费获得。不需要显式的语法“接口”。但是,您可能仍会创建一组概念性界面,例如合同(例如按合同设计)。

此外,有时将接口用于不太“纯粹”的目的。在Java中,它们可用于模拟多重继承。在C ++中,您可以使用它们来减少编译时间。

通常,它们减少了代码中的耦合。这是好事。

您的代码也可能更容易通过这种方式进行测试。


1

假设您要跟踪一些东西。所述集合必须支持一堆东西,例如添加和删除项目以及检查集合中是否有项目。

然后,可以使用方法add(),remove()和contains()指定接口ICollection。

不需要知道什么样的集合(列表,数组,哈希表,红黑树等)的代码可以接受实现该接口的对象并在不知道其实际类型的情况下使用它们。


1

在.Net中,我创建了基类,并在基类以某种方式相关时从基类继承。例如,基类Person可以被Employee和Customer继承。人员可能具有共同的属性,例如地址字段,姓名,电话等。员工可能拥有自己的部门财产。客户具有其他专有属性。

由于一个类只能从.Net中的另一个类继承,因此我将接口用于其他共享功能。有时,接口由不相关的类共享。使用接口可以创建一个契约,开发人员将知道该契约将由实现该契约的所有其他类共享。我还强迫这些类实现其所有成员。


1

在我博客的一篇文章中,我简要描述了接口的三个用途。

接口可能具有不同的用途:

  • 为同一目标提供不同的实现。典型的示例是列表,针对不同的性能用例(LinkedList,ArrayList等)可能具有不同的实现。
  • 允许条件修改。例如,基于同一算法,排序功能可以接受Comparable接口,以提供任何种类的排序标准。
  • 隐藏实施细节。这也使用户更容易阅读注释,因为在界面的主体中只有方法,字段和注释,没有很长的代码块可以跳过。

这是本文的全文:http : //weblogs.manas.com.ar/ary/2007/11/


1

在C#接口中,对于允许不共享相同基类的类多态性也非常有用。意思是,由于我们不能具有多个继承,因此可以使用接口来允许使用不同的类型。这也是允许您公开使用私有成员而无需进行反思(显式实现)的一种方法,因此它是在保持对象模型整洁的同时实现功能的一种好方法。

例如:

public interface IExample
{
    void Foo();
}

public class Example : IExample
{
    // explicit implementation syntax
    void IExample.Foo() { ... }
}

/* Usage */
Example e = new Example();

e.Foo(); // error, Foo does not exist

((IExample)e).Foo(); // success

By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.