为什么我似乎无法掌握接口?

时间:2008-09-23 18:36:09

标签: oop interface

有人可以为我揭开界面的神秘面纱,还是给我一些好的例子?我一直在这里和那里看到界面弹出,但我还没有真正接触到界面的良好解释或何时使用它们。

我在讨论接口与抽象类的上下文中的接口。

26 个答案:

答案 0 :(得分:73)

接口允许您针对“描述”而不是类型进行编程,这使您可以更松散地关联软件的元素。

以这种方式思考:您希望与旁边的多维数据集中的某人共享数据,因此您可以拔出闪存棒并进行复制/粘贴。你走到隔壁,那家伙说“那是USB吗?”你说是的 - 所有的一切。它与闪光棒的大小无关,也不是制造商 - 重要的是它是USB。

以同样的方式,界面允许您实现开发。使用另一个类比 - 想象你想要创建一个虚拟绘制汽车的应用程序。你可能有这样的签名:

public void Paint(Car car, System.Drawing.Color color)...

这将有效,直到你的客户说“现在我想画卡车”,所以你可以这样做:

public void Paint (Vehicle vehicle, System.Drawing.Color color)...

这会扩大你的应用......直到你的客户说“现在我想画房子!”你从一开始就可以做的就是创建一个界面:

public interface IPaintable{
   void Paint(System.Drawing.Color color);
}

......并将其传递给您的日常工作:

public void Paint(IPaintable item, System.Drawing.Color color){
   item.Paint(color);
}

希望这是有道理的 - 这是一个非常简单的解释,但希望能够触及它的核心。

答案 1 :(得分:45)

接口在类和调用它的代码之间建立契约。它们还允许您具有类似的类,这些类实现相同的接口但执行不同的操作或事件,而不必知道您实际使用的是哪个。这可能更有意义作为一个例子,所以让我在这里尝试一个。

假设您有几个名为Dog,Cat和Mouse的课程。这些类中的每一个都是Pet,理论上你可以从另一个名为Pet的类中继承它们,但这就是问题所在。宠物自己也不做任何事情。你不能去商店购买宠物。你可以去买狗或猫,但宠物是一个抽象的概念,而不是具体的。

所以你知道宠物可以做某些事情。他们可以睡觉,吃饭等等。所以你定义了一个名为IPet的界面,它看起来像这样(C#语法)

public interface IPet
{
    void Eat(object food);
    void Sleep(int duration);
}

您的每个Dog,Cat和Mouse类都实现了IPet。

public class Dog : IPet

所以现在每个类都必须拥有自己的Eat and Sleep实现。你有合同......现在重点是什么。

接下来假设您要创建一个名为PetStore的新对象。这不是一个非常好的PetStore,所以他们基本上只是给你一个随机的宠物(是的,我知道这是一个人为的例子)。

public class PetStore
{
     public static IPet GetRandomPet()
     {    
          //Code to return a random Dog, Cat, or Mouse
     } 
}

IPet myNewRandomPet = PetStore.GetRandomPet();
myNewRandomPet.Sleep(10);

问题是你不知道它会是什么类型的宠物。感谢界面,虽然你知道它是什么,它会吃和睡觉。

所以这个答案可能根本没有帮助,但总体思路是接口让你做一些整洁的东西,比如依赖注入和控制反转,你可以得到一个对象,有一个明确定义的对象可以做的东西列表从来没有真正知道该对象的具体类型是什么。

答案 2 :(得分:33)

最简单的答案是接口定义了您的类可以执行的操作。这是一份“合同”,表示你的班级将能够采取这种行动。

Public Interface IRollOver
    Sub RollOver()
End Interface

Public Class Dog Implements IRollOver
    Public Sub RollOver() Implements IRollOver.RollOver
        Console.WriteLine("Rolling Over!")
    End Sub
End Class

Public Sub Main()
    Dim d as New Dog()
    Dim ro as IRollOver = TryCast(d, IRollOver)
    If ro isNot Nothing Then
        ro.RollOver()
    End If
End Sub

基本上,只要它继续实现该接口,您就可以保证Dog类始终具有翻转能力。如果猫能够获得RollOver()的能力,他们也可以实现该界面,并且当他们要求RollOver()时你可以同时对待Dogs和Cats。

答案 3 :(得分:16)

当你驾驶朋友的车时,你或多或少知道如何做到这一点。这是因为传统汽车都具有非常相似的界面:方向盘,踏板等。将此界面视为汽车制造商和驾驶员之间的合同。作为一个驱动程序(软件方面的界面的用户/客户),您不需要了解不同车辆的细节就可以驾驶它们:例如,您需要知道的是转动方向盘使得车转。作为汽车制造商(以软件术语实现界面的提供商),您可以清楚地知道您的新车应该具备什么以及它应该如何表现,以便驾驶员可以在不需要额外培训的情况下使用它们。该合同是软件设计中的人称为解耦(用户与提供者)的关系 - 客户端代码是使用接口而不是其特定实现,因此不需要知道对象的细节实现界面。

答案 4 :(得分:14)

答案 5 :(得分:6)

这是一个相当“漫长”的主题,但让我试着说明一点。

接口是-as“他们命名” - 一个合同。但是忘了这个词。

理解它们的最好方法是通过某种伪代码示例。这就是我很久以前对它的理解。

假设您有一个处理消息的应用程序。消息包含一些内容,如主题,文本等。

因此,您编写MessageController以读取数据库并提取消息。在您突然听到传真即将实施之前,这是非常好的。因此,您现在必须阅读“传真”并将其作为消息处理!

这很容易变成Spagetti代码。所以你做的不是使用MessageController而只控制“Messages”,你可以使用名为IMessage的接口(我只是常用,但不是必需的)。

您的IMessage界面包含您所需的一些基本数据,以确保您能够如此处理消息。

因此,当您创建电子邮件,传真,电话通话课程时,您可以实施 IMessage 接口

因此,在MessageController中,您可以使用这样的方法:

private void ProcessMessage(IMessage oneMessage)
{
    DoSomething();
}

如果你没有使用过Interfaces,你必须拥有:

private void ProcessEmail(Email someEmail);
private void ProcessFax(Fax someFax);
etc.

因此,通过使用通用接口,您只需确保ProcessMessage方法能够使用它,无论是传真,电子邮件还是电话,等等。 / p>

为什么或如何

因为界面是合同,它指定了必须遵守(或实施)的某些内容才能使用它。将其视为徽章。如果您的对象“传真”没有IMessage接口,那么您的ProcessMessage方法将无法使用它,它将为您提供无效类型,因为您将传真传递给期望IMessage的方法对象

你明白了吗?

将界面视为您可用的方法和属性的“子集”,尽管真实对象类型。如果原始对象(Fax,Email,PhoneCall等)实现了该接口,则可以安全地将其传递给需要该接口的方法。

那里隐藏着更多魔法,你可以将界面反复回原始对象:

传真myFax =(传真)SomeIMessageThatIReceive;

.NET 1.1中的ArrayList()有一个很好的接口叫做IList。如果您有一个IList(非常“通用”),您可以将其转换为ArrayList:

ArrayList ar = (ArrayList)SomeIList;

野外有成千上万的样本。

ISortable,IComparable等接口定义了必须在您的类中实现的方法和属性,以实现该功能。

要扩展我们的示例,您可以拥有一个List<>电子邮件,传真,电话,所有都在同一个列表中,如果类型是IMessage,但如果对象只是电子邮件,传真等,则不能将它们全部放在一起。

如果要对对象进行排序(或枚举),则需要它们来实现相应的接口。在.NET示例中,如果您有一个“传真”对象列表,并希望能够使用MyList.Sort()排序它们,那么需要来制作你的传真类是这样的:

public class Fax : ISorteable 
{
   //implement the ISorteable stuff here.
}

我希望这会给你一个提示。其他用户可能会发布其他好的例子。祝好运!并拥抱INterfaces的力量。

警告:关于接口并不是一切都很好,有一些问题,OOP纯粹主义者会对此展开一场战争。我会待在一边。 Interfce(至少在.NET 2.0中)的一个缺点是,您不能拥有PRIVATE成员或受保护,必须是公开的。这有一定道理,但有时您希望您可以简单地将内容声明为私有或受保护。

答案 6 :(得分:5)

除了编程语言中的函数接口之外,在向其他表达设计思想时,它们也是一种强大的语义工具。

具有精心设计的界面的代码库突然更容易讨论。 “是的,你需要一个CredentialsManager来注册新的远程服务器。” “将PropertyMap传递给ThingFactory以获取工作实例。”

使用单个单词解决复杂事物的能力非常有用。

答案 7 :(得分:4)

接口也是多态性的关键,是“三支柱”之一。

有些人在上面提到过,多态只是意味着一个给定的类可以采用不同的“形式”。意思是,如果我们有两个类,“Dog”和“Cat”,并且都实现了接口“INeedFreshFoodAndWater”(hehe) - 你的代码可以做这样的事情(伪代码):

INeedFreshFoodAndWater[] array = new INeedFreshFoodAndWater[];
array.Add(new Dog());
array.Add(new Cat());

foreach(INeedFreshFoodAndWater item in array)
{
   item.Feed();
   item.Water();
}

这很强大,因为它允许您抽象地处理不同类别的对象,并允许您执行诸如使对象更松散耦合等操作。

答案 8 :(得分:4)

接口允许您以通用方式对对象进行编码。例如,假设您有一个发送报告的方法。现在说你有一个新的要求,你需要写一个新的报告。如果你可以重用你已经写好的方法,那会很好吗?接口使这很容易:

interface IReport
{
    string RenderReport();
}

class MyNewReport : IReport
{
    public string RenderReport()
    {
        return "Hello World Report!";

    }
}

class AnotherReport : IReport
{
    public string RenderReport()
    {
        return "Another Report!";

    }
}

//This class can process any report that implements IReport!
class ReportEmailer()
{
     public void EmailReport(IReport report)
     {
         Email(report.RenderReport());
     }
}

class MyApp()
{
    void Main()
    {
        //create specific "MyNewReport" report using interface
        IReport newReport = new MyNewReport();

        //create specific "AnotherReport" report using interface
        IReport anotherReport = new AnotherReport();

        ReportEmailer reportEmailer = new ReportEmailer();

        //emailer expects interface
        reportEmailer.EmailReport(newReport);
        reportEmailer.EmailReport(anotherReport);



    }

}

答案 9 :(得分:3)

好的,所以它是关于抽象类和接口......

从概念上讲,抽象类可以用作基类。通常它们本身已经提供了一些基本功能,并且子类必须提供它们自己的抽象方法实现(那些是未在抽象基类中实现的方法)。

接口主要用于将客户端代码与特定实现的细节分离。此外,有时在不更改客户端代码的情况下切换实现的能力使客户端代码更加通用。

在技术层面上,在抽象类和接口之间划线更难,因为在某些语言(例如,C ++)中,没有语法差异,或者因为你也可以使用抽象类来解耦或泛化。使用抽象类作为接口是可能的,因为根据定义,每个基类都定义了一个所有子类都应该遵循的接口(即,应该可以使用子类而不是基类)。

答案 10 :(得分:2)

您遇到的大多数接口都是方法和属性签名的集合。实现接口的任何人都必须提供接口中的定义。

答案 11 :(得分:2)

接口是一种强制对象实现一定功能的方法,而不必使用继承(这导致强耦合代码,而不是松散耦合,可以通过使用接口实现)。

接口描述功能,而不是实现。

答案 12 :(得分:1)

在Java中使用接口与抽象类的一个很好的理由是子类不能扩展多个基类,但它可以实现多个接口。

答案 13 :(得分:1)

接口就像一个完全抽象的类。也就是说,只有抽象成员的抽象类。您还可以实现多个接口,就像从多个完全抽象的类继承一样。无论如何..这个解释只有在你理解抽象类是什么时才有用。

答案 14 :(得分:1)

理解接口的最简单方法是首先考虑类继承的含义。它包括两个方面:

  1. 派生类的成员可以使用基类的公共成员或受保护成员作为自己的成员。
  2. 派生类的成员可以被代码使用,该代码需要基类的成员(意味着它们是可替换的)。

这两个特性都很有用,但由于很难允许类使用多个类的成员作为自己的特性,因此许多语言和框架只允许类从单个基类继承。另一方面,让一个类可以替代多个其他不相关的东西没有特别的困难。

此外,因为继承的第一个好处可以通过封装很大程度上实现,所以允许第一类型的多重继承的相对益处在某种程度上受到限制。另一方面,能够将对象替换为多种不相关类型的东西是一种有用的能力,如果没有语言支持就无法轻易实现。

接口提供了一种方法,通过该方法,语言/框架可以允许程序从多个基类型的继承的第二个方面受益,而不需要它也提供第一个。

答案 15 :(得分:1)

我遇到了和你一样的问题,我发现“合同”的解释有点令人困惑。

如果指定方法将IEnumerable接口作为in-parameter,则可以说这是一个契约,指定参数必须是从IEnumerable接口继承的类型,因此支持IEnumerable中指定的所有方法接口。如果我们使用抽象类或普通类,情况也是如此。从这些类继承的任何对象都可以作为参数传入。无论如何,无论基类是普通类,抽象类还是接口,您都可以说继承对象支持基类中的所有公共方法。

具有所有抽象方法的抽象类与接口基本相同,因此您可以说接口只是没有实现方法的类。实际上,您可以从语言中删除接口,而只使用抽象类而不使用抽象方法。我认为我们将它们分开的原因是出于语义原因,但出于编码原因,我没有看到原因并发现它只是令人困惑。

另一个建议可能是将接口重命名为接口类,因为接口只是类的另一种变体。

在某些语言中存在细微差别,允许类只继承1个类但只能继承多个接口,而在其他语言中,您可以拥有多个接口,但这是另一个问题,并且与我认为不直接相关

答案 16 :(得分:1)

简单地说:接口是一个定义了方法但没有实现的类。相比之下,抽象类具有一些实现的方法,但不是全部。

答案 17 :(得分:1)

接口是一种以强类型和多态的方式实现约定的方法。

一个好的现实世界的例子是.NET中的IDisposable。实现IDisposable接口的类强制该类实现Dispose()方法。如果类没有实现Dispose(),那么在尝试构建时会出现编译器错误。另外,这个代码模式:

using (DisposableClass myClass = new DisposableClass())
  {
  // code goes here
  }

当执行退出内部块时,将导致myClass.Dispose()自动执行。

然而,这很重要,没有强制执行Dispose()方法应该做什么。您可以让Dispose()方法从文件中选择随机配方并通过电子邮件将它们发送到分发列表,编译器并不关心。 IDisposable模式的目的是使清理资源更容易。如果一个类的实例将保存到文件句柄,那么IDisposable可以很容易地将释放和清理代码集中在一个位置,并促进一种使用方式,确保始终发生重新分配。

这是接口的关键。它们是简化编程约定和设计模式的一种方法。如果使用得当,它可以促进更简单,自我记录的代码,这些代码更易于使用,更易于维护,更正确。

答案 18 :(得分:1)

就像其他人在这里所说的那样,接口定义了一个契约(使用该接口的类将如何“看”),抽象类定义共享功能。

让我们看看代码是否有帮助:

public interface IReport
{
    void RenderReport(); // This just defines the method prototype
}

public abstract class Reporter
{
    protected void DoSomething()
    {
        // This method is the same for every class that inherits from this class
    }
}

public class ReportViolators : Reporter, IReport
{
    public void RenderReport()
    {
        // Some kind of implementation specific to this class
    }
}

public class ClientApp
{
    var violatorsReport = new ReportViolators();

    // The interface method
    violatorsReport.RenderReport();

    // The abstract class method
    violatorsReport.DoSomething();
}

答案 19 :(得分:1)

Java不允许多重继承(出于很好的理由,查找可怕的钻石)但是如果你想让你的类提供几组行为呢?假设您希望任何使用它的人知道它可以被序列化,并且它可以在屏幕上绘制自己。答案是实现两个不同的接口。

因为接口不包含它们自己的实现而没有实例成员,所以在同一个类中实现它们中的几个是安全的,没有歧义。

缺点是你必须分别在每个类中实现。因此,如果您的层次结构很简单,并且实现的某些部分对于所有继承类应该是相同的,则使用抽象类。

答案 20 :(得分:1)

简单回答:接口是一堆方法签名(+返回类型)。当一个对象说实现一个接口时,你知道它暴露了那组方法。

答案 21 :(得分:1)

假设您指的是静态类型的面向对象语言中的接口,主要用途是声明您的类遵循特定的合同或协议。

说你有:

public interface ICommand
{
    void Execute();
}
public class PrintSomething : ICommand
{
    OutputStream Stream { get; set; }
    String Content {get; set;}
    void Execute()
    { 
        Stream.Write(content);
    }
}

现在您有一个可替换的命令结构。实现IExecute的类的任何实例都可以存储在某种类型的列表中,比如说实现IEnumerable的东西,你可以遍历它并执行每一个,知道每个对象都是Just Do The Right Thing。您可以通过实现CompositeCommand来创建复合命令,CompositeCommand将有自己的命令列表来运行,或者循环命令可以重复运行一组命令,然后您将拥有大部分简单的解释器。

如果可以将一组对象缩减为它们共有的行为,则可能有理由提取接口。此外,有时您可以使用接口来防止对象意外地干扰该类的关注;例如,您可以实现一个只允许客户端检索而不是更改对象中的数据的接口,并且大多数对象只接收对检索接口的引用。

当您的接口相对简单且做出一些假设时,接口效果最佳。

查看Liskov替换原则以更好地理解这一点。

某些静态类型的语言(如C ++)不支持将接口作为第一类概念,因此您可以使用纯抽象类创建接口。

<强>更新 既然您似乎在询问抽象类与接口,那么这是我的首选过度简化:

  • 接口定义功能和特性。
  • 抽象类定义核心功能。

通常,在构建抽象类之前,我会进行提取接口重构。如果我认为应该有一个创建契约(特别是,子类应该始终支持特定类型的构造函数),我更有可能构建一个抽象类。但是,我很少在C#/ java中使用“纯”抽象类。我更有可能实现一个至少包含一个包含有意义行为的方法的类,并使用抽象方法来支持该方法调用的模板方法。然后抽象类是行为的基本实现,所有具体的子类都可以利用它而不必重新实现。

答案 22 :(得分:1)

将接口视为合同。当一个类实现一个接口时,它基本上同意遵守该合同的条款。作为消费者,您只关心您拥有的对象可以履行其合同职责。他们的内部运作和细节并不重要。

答案 23 :(得分:1)

这是我经常使用的db相关示例。我们假设您有一个对象和一个像列表一样的容器对象。让我们假设您有时可能希望以特定顺序存储对象。假设序列与数组中的位置无关,而是对象是较大对象集的子集,并且序列位置与数据库sql过滤有关。

要跟踪您的自定义序列位置,您可以使对象实现自定义界面。自定义界面可以调解维护此类序列所需的组织工作。

例如,您感兴趣的序列与记录中的主键无关。使用实现接口的对象,您可以说myObject.next()或myObject.prev()。

答案 24 :(得分:0)

接口要求任何实现它们的类都包含接口中定义的方法。

目的是在不必查看类中的代码的情况下,您可以知道它是否可以用于某个任务。例如,Java中的Integer类实现了类似的接口,因此,如果您只看到方法头(公共类String实现Comparable),您就会知道它包含compareTo()方法。

答案 25 :(得分:0)

在您的简单情况下,您可以通过使用实现show()的公共基类(或者可能将其定义为抽象)来实现与接口相似的操作。让我将您的通用名称更改为更具体的名称, Eagle Hawk ,而不是 MyClass1 MyClass2 。在这种情况下,您可以编写类似

的代码
Bird bird = GetMeAnInstanceOfABird(someCriteriaForSelectingASpecificKindOfBird);
bird.Fly(Direction.South, Speed.CruisingSpeed);

这使您可以编写可以处理 Bird 的任何内容的代码。然后,您可以编写导致 Bird 执行其事物(飞行,吃饭,产蛋等等)的代码,该代码作用于它被视为 Bird 的实例。无论 Bird Eagle Hawk ,还是从 Bird 派生的任何其他内容,该代码都能正常工作。< / p>

然而,当你没有真正的关系时,这种模式开始变得混乱。假设您想编写在天空中飞行的代码。如果您编写该代码以接受 Bird 基类,则突然变得很难将该代码发展为 JumboJet 实例,因为当 Bird JumboJet 当然都可以飞, JumboJet 肯定不是 Bird

输入界面。

Bird (以及 Eagle Hawk 的共同点是,他们都可以飞。如果您编写上述代码而不是作用于接口 IFly ,则该代码可以应用于为该接口提供实现的任何内容。