什么时候应该使用接口?

时间:2009-11-06 08:23:23

标签: oop interface

我知道界面没有正文只是方法定义。但什么时候应该使用接口?如果我向某人提供一组没有正文的接口,为什么他们觉得需要编写函数体?他们最好用抽象方法编写自己的抽象类。

编辑:

我想当你是团队的一员时,接口的使用会更多。假设A队为某事写了一个代码,他们想看看是否调用了一个方法。使用名称getRecords(),是否完成。这将帮助B队编写提供给他们的界面主体,B队必须保持方法名称相似,以便A队的代码运行。

只是一个想法。我可能错了。我认为接口对单个开发人员没用。

编辑:

谢谢大家的答案。根据你们所回复的内容,我认为当你制作像API这样的东西时,接口会有更多的用途吗?

19 个答案:

答案 0 :(得分:49)

在Java和C#等接口语言中,提供了一种以polymorphic方式拥有类的方法。也就是说一个类可以满足多个契约 - 它可以表现为多个不同的类型,一个类的类可以替代另一个类。在其他语言中,这也可以由multiple inheritance提供,但这种方法存在各种缺点。但是,让一个类表现为多个类型并不是使用接口的最常见动机。

通过编程到接口而不是类,您还可以将程序与特定实现分离。这使得将一个类实现替换为另一个类更容易。在编写单元测试时,这尤其有用,您可能希望将一些重量级类实现与轻量级mock object交换。如果你的程序只需要一个接口类型,并且重量级对象和模拟对象都实现了所述接口,那么它们很容易替换。

另外,考虑一个简单的Java示例,我说有一个程序在屏幕上显示数据页面。最初我希望它从数据库或XML文件中获取数据。如果我编写程序以便它使用接口,我可以定义一个如下界面:

public interface PageDatasource {
    public List<Page> getPages();
}

并像这样使用它:

PageDatasource datasource = // insert concrete PageDatasource implementation here
List<Pages> pages = datasource.getPages();
display(pages);

然后我可以编写符合此接口的单独数据库和XML实现:

public class DatabasePageDatasource implements PageDatasource {
    public List<Page> getPages() {
        // Database specific code
    }
}

public class XmlPageDatasource implements PageDatasource {
    public List<Page> getPages() {
        // XML specific code
    }
}

因为我使用了一个接口,所以我现在可以使用任一实现 - 数据库或XML - 可以互换使用,而无需更改我的程序中要求页面数据的部分。 XML和数据库实现可能完全不同,但我的程序关心的是提供页面数据的对象实现了PageDatasource接口。

答案 1 :(得分:24)

接口真实生活类比:

假设您要从图书馆发行图书。 你会做什么:
1)前往图书馆
2)查找您感兴趣的书籍 3)选择书籍 4)请到Librarian Desk请求他们发行本书。

现在这里的图书管理员是一个界面,这意味着你对图书馆员到底是谁不感兴趣,你只对那个坐在图书管理员桌面上的人感兴趣(就是那个同意签订合同以担任图书管理员的人,这意味着那个人同意实施图书馆员的所有行为)

所以说:图书馆员的行为是:
1)问题书
2)重新发行书籍 3)返回书。
被指定为图书馆员(意味着同意适应上述三种行为)的人必须以自己的方式实施这些行为。

让我们说PersonA想要扮演图书馆员的一部分,然后他需要适应上述3种行为。我们作为客户并不关心他是如何表现他的图书管理员行为的,我们只对那个人感兴趣A是图书管理员并且他会遵守图书管理员的任务。
因此,你将要做的是,参考接口[去图书馆员办公室(这将引导你作为图书管理员的人)]并且让我们说在未来一个人离开了图书馆员的帖子,然后 作为客户,如果你接近图书管理员办公桌,而不是特定的人充当图书管理员,它不会影响你。所以代码接口而不是具体是有益的。

class PersonA implements Librarian {
    public void IssueBook(){...}
    public void ReIssueBook(){...}
    public void ReturnBook(){...}

    //Other person related tasks...
}   

答案 2 :(得分:9)

即使单个开发人员界面也是一个非常有用的工具。让我试着用一个例子来说明。

假设您正在开发一个管理图书馆目录的系统,图书馆将借出图书和DVD。您决定创建类预订 Dvd 来模拟已借出的项目,但现在您要实现多态性,以便您可以处理项目而不是书籍或DVD。问题是 Item 应该是抽象类还是接口?

在这种情况下,您可能希望使用抽象类,因为父类可以提供 Book Dvd 共有的功能,例如检查退出或退回物品。

现在假设您要为库目录实现持久性机制。您已决定要将某些数据存储在数据库中,将某些数据存储在XML文件中,将某些数据存储在逗号分隔的文件中。所以现在的问题是如何以多态方式实现这一点,以便您可以处理一般的持久性API?

在这种情况下,您应该定义一个可以由每个提供数据库,XML和逗号分隔持久性的类实现的接口,因为每个持久性机制都提供类似的功能,即存储和检索数据,但每个都将实现非常不同。这将允许您轻松更改正在使用的持久性机制,而无需对使用持久性机制的代码进行大量更改。

答案 3 :(得分:8)

接口有助于阐明不同功能单元之间的区别。一个单元依赖于另一个单元来 某些东西,而不是 。只要其他人可以 界面中规定的内容(想想合同),那么 可以 幕后的任何内容。

例如,我有一个入口处理器,它从一个地方读取条目,并将它们写入另一个地方。它不关心什么/在哪里,或在什么/哪里。所有它关心的是它从一些类型的阅读器(使用 IReader 接口)进入一侧,并将它们交给某种类型的编写器(使用 IWriter 界面)。

在我的第一个实现中, IReader 实现者从SQL数据库中获取内容, IWriter 实现者通过Web服务客户端发布它。但是,我们最终会在两端创建其他实现者来访问其他存储库(FTP站点,本地网络驱动器上的文件目录等)。

此时中间的处理器并不关心也不会改变。它只是通过这些标准接口进行讨论。

理论上你可以使用一个基类(最好是一个 abstract 一个)而不是一个接口,但是这会开始将你的模块更紧密地锁在一起,这使你的系统更难维护。即使你不是一个程序员团队,失去耦合确实会让你的生活变得更轻松。每次在系统的不同部分工作时,请将自己视为不同的程序员。每次重新访问给定的部分时,您都必须重新学习它所做的事情来维护它。如果你的系统的每一部分都与其他部分紧密耦合,那么你必须对整个系统有一个恒定,深入的了解,而不仅仅是你正在工作的那部分。

还有一个实现多个接口的单个​​类的概念,这几乎就像多重继承一样。在这种情况下,单个班级可以执行多个工作(可以说这不是很明智,但至少可以这样做)。如果您选择使用基类而不是上面的接口,那么这是不可能的。

答案 4 :(得分:7)

乍一看,抽象类和接口看起来很简单。为什么只提供一个接口,当你还可以提供一些基础实现?经过调查,你会发现还有更多内容。

也就是说,使用接口有很多原因。你可以找到一篇关于差异here的好文章。

那就是说,考虑一下你可以创建一个接口(一个“合同”,表示你的类肯定支持某些调用/方法签名调用),但你只能提供一个抽象类。还要考虑这样一个事实,即您可以创建一个也实现一个或多个接口的抽象类,并从中继承。这不是边缘情况。这实际上是在API中经常完成的,意味着具有高扩展性。

查看我指出的博客文章,您应该彻底了解何时使用它们以及为什么要使用它们。我还强烈推荐一本好书,例如Microsoft Press的“CLR via C#”。你会学到很多东西!

答案 5 :(得分:7)

接口存在的原因是由于OOP的两个主要概念,即“身份”和“功能”

类具有功能和身份。通过继承,实例化的对象可以具有许多功能和多个身份。

接口是没有功能的标识。该功能将由实例化的类提供。

第三种形式,“mixin”是没有身份的功能。像ruby这样的编程语言提供了第三种继承形式。

如何使用界面因编程语言的上下文和您的情况而异,但请记住,界面用于定义强制执行到对象的身份。

答案 6 :(得分:4)

接口优于抽象类,因为您可以实现多个接口,并且只能从一个抽象类继承。

所以你可以这样做:

class MyRow extends AbstractRow implements ISearchable, ISortable
{

}

此外,搜索StackOverflow以查找其他类似问题,例如Need of interfaces in c#

答案 7 :(得分:4)

要添加到以前的答案,接口可以帮助您在单元测试期间允许您将基于接口的模拟对象注入到代码中,从而允许您模拟特定方案并将单元测试与特定组件隔离,而无需依赖关于外部组件。

例如,假设您有一个业务逻辑类,它使用数据逻辑类从数据源检索数据然后处理它。通过为它继承的数据逻辑类创建一个接口,您可以根据接口创建该类的模拟/伪实例,并将其注入到您进行单元测试的业务逻辑类中。可以定义模拟实例,以期望某些方法/属性调用,在某些点抛出异常,返回有效输出等等。这意味着,您的单元测试可以更快/更可靠地运行,因为它们不依赖于底层数据源可用/实际上不必连接到它。而且你将单元测试隔离到特定的代码单元。

答案 8 :(得分:2)

让我们假设我有类FoodEstablishment,现在我想做简单的CRUD操作,所以我该怎么做? 我定义了一个服务接口,但为什么呢?

public interface IFoodEstablishmentService
{
    Task<int> AddAsync(FoodEstablishment oFoodEstablishment);
    FoodEstablishment SelectByFoodEstablishmentId(long id);
    Task<int> UpdateAsync(FoodEstablishment oFoodEstablishment);
    Task<int> DeleteAsync(FoodEstablishment oFoodEstablishment);
}

然后我将为该特定服务实现该合同或接口

public class FoodEstablishmentService : IFoodEstablishmentService
{
    public async Task<int> AddAsync(FoodEstablishment oFoodEstablishment)
    {
       // Insert Operation
        return result;
    }

    public FoodEstablishment SelectByFoodEstablishmentId(long id)
    {
        // Select Logic
        return oFoodEstablishment;
    }

    public async Task<int> UpdateAsync(FoodEstablishment oFoodEstablishment)
    {
        // Update Logic
        return result;
    }

    public async Task<int> DeleteAsync(FoodEstablishment oFoodEstablishment)
    {
        // Delete Logic
        return result;
    }

}

所以在我的主程序或我希望使用Service的地方,我会做

IFoodEstablishmentService oFoodEstablishmentService =  new FoodEstablishmentService();
FoodEstablishment oFoodEstablishment = // Input might be from views;
oFoodEstablishmentService.AddAsync(oFoodEstablishment);

所以到目前为止,这似乎是一个额外的步骤,当我们可以直接完成

FoodEstablishmentService oFoodEstablishmentService =  new FoodEstablishmentService();
FoodEstablishment oFoodEstablishment = // Input might be from views;
oFoodEstablishmentService.AddAsync(oFoodEstablishment);

但是如果我可能需要通过队列而不是直接向服务器传递插入逻辑,等待插入操作完成然后返回结果,而不是传递队列然后队列工作者处理那些操作,可能不是排队插入的最佳想法,但是对于接口示例肯定是好的:-)。所以现在我要做的是,创建另一个实现相同合同IFoodEstablishment的类FoodEstablishment。

public class FoodEstablishmentQueueService : IFoodEstablishmentService
{
    public async Task<int> AddAsync(FoodEstablishment oFoodEstablishment)
    {
       // Insert Queue Operation
        return result;
    }

    public FoodEstablishment SelectByFoodEstablishmentId(long id)
    {
        // Select Queue Logic
        return oFoodEstablishment;
    }

    public async Task<int> UpdateAsync(FoodEstablishment oFoodEstablishment)
    {
        // Update Queue Logic
        return result;
    }

    public async Task<int> DeleteAsync(FoodEstablishment oFoodEstablishment)
    {
        // Delete Queue Logic
        return result;
    }
}

所以现在如果我想使用队列版本,我会做

IFoodEstablishmentService oFoodEstablishmentService =  new FoodEstablishmentQueueService();
FoodEstablishment oFoodEstablishment = // Input might be from views;
oFoodEstablishmentService.AddAsync(oFoodEstablishment);

我们可以用旧的方式使用类来实现,但是使用特定的类来限制类实例化,这对于扩展是一种严格的,现在FoodEstablishmentQueueService也可以做其他事情,创建另一个方法直到合同有效所以接口是联系是为了保持一致性,想象一个正在进行正常版本的人和其他正在做队列版本的人或有人正在做缓存版本,可能会有关于签名的问题,除非它预先指定了工作合同并且人们不会最终交叉检查所有内容。 / p>

同样,让我们​​考虑使用IEnumerable等预定义类型的其他简单示例。假设我传递了一个FoodEstablishment集合列表并返回自定义排序列表

public FoodEstablishment[] SortFoodEstablishment(FoodEstablishment[] list)
{
    foreach(var oFoodEstablishment in list)
    {
    // some logic
    }
    return sortedList;
}

所以我们会这样使用它

FoodEstablishment[] list = new FoodEstablishment[]{ //some values }
var listSorted = oFoodEstablishmentService.SortFoodEstablishment(list);

但是如果我们发送列表而不是数组

呢?
List<FoodEstablishment> list = //some values;
var listSorted = oFoodEstablishmentService.SortFoodEstablishment(list);

我们会收到错误,因为它使用类严格实现,所以我们使用IEnumerable&lt;&gt;这是由List实现的,基本上删除了从List到接口的依赖

public IEnumerable<FoodEstablishment> SortFoodEstablishment(IEnumerable<FoodEstablishment> list)
{
    foreach(var oFoodEstablishment in list)
    {
    // some logic
    }
    return sortedList;
}

所以通常的实施很大程度上取决于情况

答案 9 :(得分:2)

接口和抽象类有不同的用途。 首先,抽象类只适用于继承 - 它不能直接实例化。您可以将实现代码放在抽象类中,但只能从扩展它的类或通过扩展它的类调用该代码。

示例:

public abstract class A {
    public void myFunc() {
        //do some work
    }
}

public class B : A {
    private void someOtherFunc() {
        base.myFunc();
    }
}

public class Z {
    public void exampleFunc() {
        //call public base class function exposed through extending class:
        B myB = new B();
        myB.myFunc();

        //explicitly cast B to A:
        ((A) myB).myFunc();

        //'A' cannot be created directly, do implicit cast down to base class:
        A myA = new B();
        myA.myFunc();
    }
}

接口的目的是提供契约 - 这意味着实现它的类具有来为接口中声明的属性或函数提供实现,并且它必须使用完全相同的功能签名。当我编写一些调用实现接口的类的代码时,这提供了保证,并且它与派生的内容无关,甚至是它们写入的语言,或者我从哪里获取它们。另一个很酷的功能是我可以为不同的接口提供相同功能签名的几种不同实现。请查看此示例,该示例使用上一个示例中的一些相同类:

public class Z {
    public void exampleFunc() {
        C myC = new C();
        //these two calls both call the same function:
        myC.myFunc();
        ((IExampleInterface1)myC).myFunc();

        D myD = new D();
        //hmmm... try and work out what happens here, which myFunc() gets called?
        //(clue: check the base class)
        myD.myFunc();

        //call different myFunc() in same class with identical signatures:
        ((IExampleInterface1)myD).myFunc();
        ((IExampleInterface2)myD).myFunc();
    }
}

interface IExampleInterface1
{
    void myFunc();
}

interface IExampleInterface2
{
    void myFunc();
}

public class C : IExampleInterface1
{
    public void myFunc()
    {
        //do stuff
    }
}

public class D : A, IExampleInterface1, IExampleInterface2
{
    void IExampleInterface1.myFunc()
    {
        //this is called when D is cast as IExampleInterface1
    }

    void IExampleInterface2.myFunc()
    {
        //this is called when D is cast as IExampleInterface2
    }
}

答案 10 :(得分:2)

这是设计问题(我讲的是java世界)。

该界面允许您定义软件组件(类)的行为和结构,而不会在运行时给出约束。

相反,抽象类为您提供了一个方法的默认行为:如果您确定此代码在应用程序的时间内不会发生变化,则会显示该行为。

示例:

您有一个商业系统的网络应用程序,可以在某些情况下发送电子邮件(注册新用户)。

如果你确定行为不会改变,你可以使用抽象类 SenderCommunication 和方法boolean sendWithSuccefull(...)。

如果你不确定这种行为不会经常改变,你可以使用接口 InterfaceSenderCommunication 和方法boolean sendWithSuccefull(...)。

当然,“经常改变 - 不经常改变”的判断取决于2个要素:

  • 我必须花多少时间进行同步 新规范的旧代码?
  • 客户支付多少钱? ;)

答案 11 :(得分:1)

使用接口的一个原因是类会实现许多接口。抽象类不能这样做。

一个例子是一个处理鼠标移动的类,按键将实现(虚构的)IMouseMove和IKeyPress接口。

答案 12 :(得分:1)

接口保持定义和实现独立。如果您想要供应商特定实现,那么这是最好的方法。

现实生活中的例子是JDBC如果你看到API Statemnet,PreparedStatemnet Everything是接口,但实现是供应商特定的,像Oracle ojdbc jar有一些其他实现,mysql还有其他一些。

如果您想要更改数据库,您只需在连接类名称中输入驱动程序nad。这是使用接口

的优点

答案 13 :(得分:1)

在Set类型的变量中存储对HashSet或TreeSet的引用被认为是好的样式。

Set<String> names = new HashSet<String>();

这样,如果您决定使用TreeSet,则只需更改一行。

此外,对集合进行操作的方法应指定Set:

类型的参数

public static void print(Set<String> s)

然后该方法可用于所有集合实现。

理论上,我们应该对链表做出相同的建议,即保存 Linked类型中的LinkedList引用。但是,在Java库中,List接口对ArrayListLinkedList类都是通用的。特别是,它具有获取和设置随机访问的方法,即使这些方法对于链表非常低效。

如果您不知道随机访问是否有效,则无法编写有效的代码。 这显然是标准库中的一个严重的设计错误,我不建议使用 因为这个原因的List接口。

(看看这个错误有多尴尬,看看吧 Collections类的binarySearch方法的源代码。那个方法需要一个 列表参数,但二进制搜索对链表没有意义。然后笨拙的代码 尝试发现列表是否是链表,然后切换到线性搜索!)

Set界面和Map界面设计得很好,您应该使用它们。

答案 14 :(得分:1)

抽象类v / s接口始终是开发人员之间的讨论点。我会加5美分。 如果要扩展comman基础以及要为抽象方法提供默认实现的位置,请使用抽象类。

当您想要实现接口的类的所有抽象方法时,请使用接口,并且不能提供方法的默认主体。

答案 15 :(得分:1)

Java lang不支持多重继承,因为接口用于实现目标。

对于一个抽象的类,只有一个方法必须是抽象的;而在这种情况下 接口所有方法都是抽象的。

答案 16 :(得分:1)

此外,使用接口可以简化单元测试。为了测试依赖于接口的类,并且只要使用某种依赖注入,就可以创建实现依赖接口的存根类,或者可以使用模拟引擎。

答案 17 :(得分:0)

界面的使用:

  1. 定义 合同
  2. 使用 功能 链接不相关的类(例如,实现Serializable接口的类可能有也可能没有任何关系,除了实现该接口
  3. 提供可互换的实施,例如Strategy_pattern
  4. 如果您正在寻找Java作为编程语言,那么自从Java-8发布以来,interface在添加defaultstatic方法方面几乎没有其他功能。

    有关详细信息,请参阅此问题:

    Why to use Interfaces, Multiple Inheritance vs Interfaces, Benefits of Interfaces?

答案 18 :(得分:0)

属于一个类别的对象可以使用接口。当对象之间存在IS-A关系时。

E.g。文字,图片,MP3,FLV,MOV属于文件类型类别

如果我们的应用程序允许用户上传4种文件类型,则它们都属于一个名为文件类型

的类别
        
  1. 文本
  2.     
  3. 图像
  4.     
  5. 音频
  6.     
  7. 视频
  8. 在内部代码中,我们将拥有上述每种类型的对象。

    enter image description here

    假设我们需要发送文件

    使用Out界面

    enter image description here

    使用界面

    enter image description here

    因此我们可以假设所有文件类型(文本,音频等)都是FileFormat类型。因此我们形成了一个IS-A关系。

    这有助于返回单个数据类型而不是从函数返回特定类型时我们可以发送FileFormat的常见类型。

    enter image description here