77

我知道他们强迫您实施方法等,但我不明白您为什么要使用它们。谁能给我一个很好的例子或解释我为什么要实现这个。

4

19 回答 19

68

一个具体的例子:接口是一种指定其他人的代码必须满足的契约的好方法。

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

然后任何随机的人都可以出现,创建一个实现该接口的类,实例化该类的一个对象并将其传递给我的库代码并期望它能够工作。注意:当然可以严格实现一个接口而忽略接口的意图,所以仅仅实现一个接口并不能保证事情会成功。笨蛋总有办法!:-)

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

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

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

于 2008-10-27T14:56:05.773 回答
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:我知道这些例子有点天真,但它们是例子......

于 2008-10-27T14:57:48.500 回答
10

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

于 2008-10-27T14:58:11.213 回答
10

理解接口的最简单方法是它们允许不同的对象公开 COMMON 功能。这允许程序员编写更简单、更短的代码来编程到接口,然后只要对象实现了该接口,它就可以工作。

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

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

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

示例 2: 如果您注意到几乎所有集合都实现了这个称为 IEnumerable 的接口。IEnumerable 返回具有 MoveNext()、Current 和 Reset() 的 IEnumerator。这使 C# 可以轻松地在您的集合中移动。它可以这样做的原因是因为它公开了 IEnumerable 接口,它知道该对象公开了它需要通过它的方法。这有两件事。1) foreach 循环现在将知道如何枚举集合 2) 您现在可以将强大的 LINQ 表达式应用于您的集合。同样,接口在这里如此有用的原因是因为所有集合在 COMMON 中都有一些东西,它们可以被移动。每个集合可以通过不同的方式移动(链表与数组),但接口的美妙之处在于实现是隐藏的,与接口的使用者无关。MoveNext() 为您提供集合中的下一个项目,不管它是如何做到的。很不错吧?

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

当然,我必须给出我最喜欢的 C++ 多态示例,动物示例。所有动物都具有某些特征。可以说他们可以移动,说话,而且他们都有一个名字。因为我刚刚确定了我所有动物的共同点,我可以将这些特性抽象到 IAnimal 接口中。然后我创建了一个 Bear 对象、一个 Owl 对象和一个 Snake 对象,它们都实现了这个接口。您可以将实现相同接口的不同对象存储在一起的原因是因为接口表示 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
}

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

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

希望这可以帮助。

于 2012-02-29T08:16:10.383 回答
5

The pedals on a car implement an interface. I'm from the US where we drive on the right side of the road. Our steering wheels are on the left side of the car. The pedals for a manual transmission from left to right are clutch -> brake -> accelerator. When I went to Ireland, the driving is reversed. Cars' steering wheels are on the right and they drive on the left side of the road... but the pedals, ah the pedals... they implemented the same interface... all three pedals were in the same order... so even if the class was different and the network that class operated on was different, i was still comfortable with the pedal interface. My brain was able to call my muscles on this car just like every other car.

Think of the numerous non-programming interfaces we can't live without. Then answer your own question.

于 2008-10-27T15:04:05.773 回答
5
When you need different classes to share same methods you use Interfaces.
于 2012-02-29T08:22:13.743 回答
4

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

一个经典的例子可能是 IVehicle,它有一个 Move() 方法。您可以拥有实现 IVehicle 的 Car、Bike 和 Tank 类。它们都可以 Move(),而且你可以编写不关心它正在处理什么样的车辆的代码,这样它就可以 Move()。

void MoveAVehicle(IVehicle vehicle)
{
    vehicle.Move();
}
于 2008-10-27T14:52:28.527 回答
4

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

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

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

C 的文件描述符基本上是一个接口“我可以读取和/或写入字节和/或写入的东西”,几乎每种类型的语言都有这样的接口潜伏在其标准库中:流或其他任何东西。无类型语言通常具有表示流的非正式类型(可能称为协定)。所以在实践中,您几乎不必自己实际发明这个特定的界面:您使用语言提供给您的东西。

日志和流只是一个例子——只要你可以用抽象的术语描述一个对象应该做什么,但又不想将它与特定的实现/类/任何东西联系起来,就会出现接口。

于 2008-10-27T14:58:07.983 回答
3

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

例如,如果你想编写一个类似快速排序的排序算法,你只需要对任何对象列表进行排序,就可以成功地比较两个对象。如果你创建一个接口,比如 ISortable,那么任何创建对象的人都可以实现 ISortable 接口,并且他们可以使用你的排序代码。

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

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

于 2008-10-27T14:52:04.987 回答
3

Imagine the following basic interface which defines a basic CRUD mechanism:

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

From this interface, you can tell that any object that implements it, must have functionality to create, read, update and delete data. This could by a database connection, a CSV file reader, and XML file reader, or any other kind of mechanism that might want to use CRUD operations.

Thus, you could now have something like the following:

class Logger {
    Storable storage;

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

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

This logger doesn't care if you pass in a database connection, or something that manipulates files on disk. All it needs to know is that it can call create() on it, and it'll work as expected.

The next question to arise from this then is, if databases and CSV files, etc, can all store data, shouldn't they be inherited from a generic Storable object and thus do away with the need for interfaces? The answer to this is no... not every database connection might implement CRUD operations, and the same applies to every file reader.

Interfaces define what the object is capable of doing and how you need to use it... not what it is!

于 2008-10-27T15:15:26.140 回答
2

In an article in my blog I briefly describe three purposes interfaces have.

Interfaces may have different purposes:

  • Provide different implementations for the same goal. The typical example is a list, which may have different implementations for different performance use cases (LinkedList, ArrayList, etc.).
  • Allow criteria modification. For example, a sort function may accept a Comparable interface in order to provide any kind of sort criteria, based on the same algorithm.
  • Hide implementation details. This also makes it easier for a user to read the comments, since in the body of the interface there are only methods, fields and comments, no long chunks of code to skip.

Here's the article's full text: http://weblogs.manas.com.ar/ary/2007/11/

于 2008-10-27T15:10:27.773 回答
2

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

于 2008-10-29T03:03:03.433 回答
1

我认为你需要对设计模式有一个很好的理解,这样才能看到力量。

查看 Head First 设计模式

于 2008-10-27T16:39:56.733 回答
1

正如您所指出的,当您想强制某人以某种格式制作时,界面很有用。

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

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

因此很少使用接口,因为通常您可以做出假设,或者不需要数据来完成您需要做的事情。

于 2008-10-27T14:56:15.633 回答
1

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

于 2008-10-27T14:57:24.227 回答
1

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

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

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

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

于 2008-10-27T14:57:32.463 回答
1

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

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

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

于 2008-10-27T14:58:35.813 回答
1

In .Net, I create base classes and inherit from them when the classes are somehow related. For example, base class Person could be inherited by Employee and Customer. Person might have common properties like address fields, name, telephone, and so forth. Employee might have its own department property. Customer has other exclusive properties.

Since a class can only inherit from one other class in .Net, I use interfaces for additional shared functionality. Sometimes interfaces are shared by classes that are otherwise unrelated. Using an interface creates a contract that developers will know is shared by all of the other classes implementing it. I also forces those classes to implement all of its members.

于 2008-10-27T15:02:47.693 回答
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
于 2008-10-27T15:47:11.677 回答