241

接口允许您创建定义实现它的类的方法的代码。但是,您不能向这些方法添加任何代码。

抽象类允许您做同样的事情,以及向方法添加代码。

现在如果你可以用抽象类实现同样的目标,为什么我们还需要接口的概念呢?

有人告诉我,它与从 C++ 到 Java 的 OO 理论有关,这是 PHP 的 OO 东西所基于的。这个概念在 Java 中有用但在 PHP 中没有用吗?这只是一种避免在抽象类中乱扔占位符的方法吗?我错过了什么吗?

4

15 回答 15

148

接口的全部意义在于让您灵活地让您的类强制实现多个接口,但仍然不允许多重继承。从多个类继承的问题多种多样,维基百科页面很好地总结了这些问题。

接口是一种妥协。多重继承的大多数问题不适用于抽象基类,因此现在大多数现代语言禁用多重继承,但调用抽象基类接口并允许一个类“实现”任意数量的那些。

于 2008-08-21T16:45:54.390 回答
128

这个概念在面向对象编程中非常有用。对我来说,我认为接口是一种合同。只要我的班级和你的班级就这个方法签名合同达成一致,我们就可以“接口”。至于抽象类,我认为它们更像是一些基类,它们存根一些方法,我需要填写细节。

于 2008-08-21T16:39:23.143 回答
81

如果已经有抽象类,为什么还需要接口? 防止多重继承(可能导致多个已知问题)。

此类问题之一:

“钻石问题”(有时被称为“致命的死亡钻石”)是当两个类 B 和 C 继承自 A 而类 D 继承自 B 和 C 时出现的歧义。如果 A 中有一个方法B 和 C 都覆盖了,D 没有覆盖,那么 D 继承了哪个版本的方法:B 的,还是 C 的?

来源:https ://en.wikipedia.org/wiki/Multiple_inheritance#The_diamond_problem

为什么/何时使用接口? 一个例子……世界上所有的汽车都有相同的界面(方法)…… AccelerationPedalIsOnTheRight(),。BrakePedalISOnTheLeft()想象一下,每个汽车品牌都会有这些不同于另一个品牌的“方法”。宝马将在右侧安装制动器,而本田将在车轮左侧安装制动器。人们每次购买不同品牌的汽车时都必须了解这些“方法”是如何工作的。这就是为什么在多个“地方”拥有相同的界面是个好主意。

界面对你有什么作用(为什么有人会使用一个)?接口可以防止您犯“错误”(它向您保证所有实现特定接口的类都将具有接口中的方法)。

// Methods inside this interface must be implemented in all classes which implement this interface.
interface IPersonService
{   
    public function Create($personObject);
}

class MySqlPerson implements IPersonService
{
    public function Create($personObject)
    {
        // Create a new person in MySql database.
    }
}

class MongoPerson implements IPersonService
{
    public function Create($personObject)
    {
        // Mongo database creates a new person differently then MySQL does. But the code outside of this method doesn't care how a person will be added to the database, all it has to know is that the method Create() has 1 parameter (the person object).
    }
}

这样,该Create()方法将始终以相同的方式使用。我们使用的是MySqlPerson类还是MongoPerson类都没有关系。我们使用方法的方式保持不变(界面保持不变)。

例如,它将像这样使用(在我们的代码中的任何地方):

new MySqlPerson()->Create($personObject);
new MongoPerson()->Create($personObject);

这样,这样的事情就不会发生:

new MySqlPerson()->Create($personObject)
new MongoPerson()->Create($personsName, $personsAge);

记住一个界面并在任何地方使用相同的界面比使用多个不同的界面要容易得多。

这样,Create()对于不同的类,方法的内部可以不同,而不会影响调用该方法的“外部”代码。所有外部代码都必须知道该方法Create()有 1 个参数 ( $personObject),因为这就是外部代码使用/调用该方法的方式。外部代码并不关心方法内部发生了什么;它只需要知道如何使用/调用它。

您也可以在没有接口的情况下执行此操作,但是如果您使用接口,它会“更安全”(因为它可以防止您犯错误)。该接口确保该方法Create()在实现该接口的所有类中具有相同的签名(相同的类型和相同数量的参数)。通过这种方式,您可以确保任何实现IPersonService接口的类都将具有方法Create()(在本例中)并且只需要 1 个参数 ( $personObject) 即可被调用/使用。

实现接口的类必须实现接口所做/拥有的所有方法。

我希望我没有过多地重复自己。

于 2014-06-26T17:11:40.803 回答
25

对我来说,使用接口和抽象类之间的区别更多地与代码组织有关,而不是语言本身的强制执行。在为其他开发人员准备代码以使他们保持在预期的设计模式内时,我经常使用它们。接口是一种“契约式设计”,您的代码同意响应一组规定的 API 调用,这些调用可能来自您无权访问的代码。

虽然从抽象类继承是“是”关系,但这并不总是您想要的,实现接口更像是“行为”关系。在某些情况下,这种差异可能非常显着。

例如,假设您有一个抽象类 Account,许多其他类都从该类扩展(帐户类型等)。它具有一组仅适用于该类型组的特定方法。但是,其中一些帐户子类实现了 Versionable、Listable 或 Editable,以便可以将它们放入期望使用这些 API 的控制器中。控制器不关心它是什么类型的对象

相比之下,我也可以创建一个不从 Account 扩展的对象,比如一个 User 抽象类,并且仍然实现 Listable 和 Editable,但不是 Versionable,这在这里没有意义。

通过这种方式,我是说 FooUser 子类不是一个帐户,但它的行为就像一个 Editable 对象。同样,BarAccount 从 Account 扩展而来,但不是 User 子类,而是实现了 Editable、Listable 和 Versionable。

将所有这些用于 Editable、Listable 和 Versionable 的 API 添加到抽象类本身不仅会变得混乱和丑陋,而且会复制 Account 和 User 中的公共接口,或者强制我的 User 对象实现 Versionable,可能只是为了抛出一个例外。

于 2008-08-27T16:36:48.217 回答
23

接口本质上是您可以创建的蓝图。它们定义了类必须具有的方法,但您可以在这些限制之外创建额外的方法。

我不确定你不能向方法添加代码是什么意思——因为你可以。您是将接口应用于抽象类还是扩展它的类?

应用于抽象类的接口中的方法需要在该抽象类中实现。但是将该接口应用于扩展类,并且该方法只需要在扩展类中实现。我在这里可能是错的——我没有尽可能/应该经常使用接口。

我一直认为接口是外部开发人员的一种模式或一个额外的规则集,以确保事情是正确的。

于 2008-08-21T16:44:38.467 回答
15

您将在 PHP 中使用接口:

  1. 隐藏实现 - 建立对一类对象的访问协议,更改底层实现,而无需在您使用该对象的所有地方进行重构
  2. 检查类型 - 如确保参数具有特定类型$object instanceof MyInterface
  3. 在运行时强制执行参数检查
  4. 将多个行为实现到单个类中(构建复杂类型)

    类汽车实现EngineInterface,BodyInterface,SteeringInterface {

这样一个Car对象现在可以start(), stop()(EngineInterface) 或goRight(), goLeft()(Steering interface)

以及其他我现在想不到的事情

第 4 点,它可能是您无法使用抽象类解决的最明显的用例。

来自 Java 的思考:

一个接口说:“这就是实现这个特定接口的所有类的样子。” 因此,任何使用特定接口的代码都知道可以为该接口调用哪些方法,仅此而已。所以接口是用来在类之间建立“协议”的。

于 2013-02-08T18:20:37.053 回答
12

接口的存在不是作为类可以扩展的基础,而是作为所需功能的映射。

以下是使用抽象类不适合的接口的示例:
假设我有一个日历应用程序,它允许用户从外部源导入日历数据。我会编写类来处理导入每种类型的数据源(ical、rss、atom、json),这些类中的每一个都将实现一个通用接口,以确保它们都具有我的应用程序获取数据所需的通用公共方法。

<?php

interface ImportableFeed 
{
    public function getEvents();
}

然后,当用户添加新提要时,我可以识别提要的类型并使用为该类型开发的类来导入数据。为特定提要导入数据而编写的每个类都将具有完全不同的代码,否则这些类之间可能几乎没有相似之处,除非它们需要实现允许我的应用程序使用它们的接口。如果我要使用抽象类,我可以很容易地忽略这样一个事实,即我没有重写 getEvents() 方法,这会在这个实例中破坏我的应用程序,而使用接口不会让我的应用程序运行,如果任何方法接口中定义的在实现它的类中不存在。我的应用不必关心它使用什么类从提要中获取数据,

为了更进一步,当我回到我的日历应用程序并打算添加另一种提要类型时,该界面被证明是非常有用的。使用 ImportableFeed 接口意味着我可以通过简单地添加实现此接口的新类来继续添加更多导入不同提要类型的类。这允许我添加大量功能而不必向我的核心应用程序添加不必要的批量,因为我的核心应用程序只依赖于接口所需的可用公共方法,只要我的新提要导入类实现 ImportableFeed 接口,然后我知道我可以把它放在适当的位置并继续前进。

这只是一个非常简单的开始。然后,我可以创建另一个接口,我的所有日​​历类都需要实现该接口,该接口提供更多特定于类处理的提要类型的功能。另一个很好的例子是验证提要类型等的方法。

这超出了问题的范围,但是因为我使用了上面的示例:如果以这种方式使用接口,则会出现一系列问题。我发现自己需要确保从实现的方法返回的输出与接口匹配并实现这一点我使用读取 PHPDoc 块的 IDE 并将返回类型作为类型提示添加到接口的 PHPDoc 块中,然后翻译成实现它的具体类。使用实现此接口的类的数据输出的我的类将至少知道它期望在此示例中返回一个数组:

<?php
interface ImportableFeed 
{
    /**
     * @return array
     */
    public function getEvents();
}

没有太多空间可以比较抽象类和接口。接口只是映射,在实现时要求类具有一组公共接口。

于 2015-07-30T19:23:51.920 回答
9

接口不仅仅用于确保开发人员实现某些方法。这个想法是因为这些类保证有某些方法,即使你不知道类的实际类型,你也可以使用这些方法。例子:

interface Readable {
  String read();
}

List<Readable> readables; // dunno what these actually are, but we know they have read();
for(Readable reader : readables)
  System.out.println(reader.read());

在许多情况下,提供基类(无论是否抽象)都没有意义,因为实现方式千差万别,除了一些方法外,没有任何共同点。

动态类型语言具有不需要接口的“鸭子类型”的概念;您可以自由假设该对象具有您正在调用的方法。这可以解决静态类型语言中的问题,其中您的对象有一些方法(在我的示例中为 read()),但不实现接口。

于 2008-08-21T16:52:24.203 回答
7

在我看来,接口应该优于非功能性抽象类。如果那里甚至会影响性能,我不会感到惊讶,因为只有一个对象实例化,而不是解析两个,将它们组合起来(尽管我不能确定,我不熟悉内部工作原理面向对象的 PHP)。

与 Java 相比,接口确实没有那么有用/有意义。另一方面,PHP6 将引入更多类型提示,包括返回值的类型提示。这应该为 PHP 接口增加一些价值。

tl; dr:接口定义了需要遵循的方法列表(想想 API),而抽象类提供了一些基本/通用功能,子类根据特定需求对其进行细化。

于 2008-08-21T16:47:11.253 回答
6

我不记得PHP在这方面是否有所不同,但是在Java中可以实现多个接口,但不能继承多个抽象类。我假设 PHP 的工作方式相同。

在 PHP 中,您可以通过用逗号分隔多个接口来应用多个接口(我认为,我认为这不是一个干净的解决方案)。

至于多个抽象类,您可以有多个相互扩展的抽象(同样,我对此并不完全确定,但我想我以前在某处见过)。唯一不能扩展的是 final 类。

于 2008-08-21T16:53:24.810 回答
5

接口不会给您的代码带来任何性能提升或类似的东西,但它们可以大大有助于使其可维护。确实可以使用抽象类(甚至是非抽象类)来为您的代码建立接口,但是正确的接口(您使用关键字定义且仅包含方法签名的接口)更容易整理并阅读。

话虽如此,在决定是否在类上使用接口时,我倾向于使用自由裁量权。有时我想要默认方法实现,或所有子类共有的变量。

当然,关于多接口实现的观点也是一个合理的观点。如果您有一个实现多个接口的类,您可以在同一个应用程序中将该类的对象用作不同类型。

但是,您的问题是关于 PHP 的这一事实使事情变得更有趣。在 PHP 中键入接口仍然不是非常必要的,您几乎可以将任何内容提供给任何方法,无论其类型如何。您可以静态键入方法参数,但其中一些已损坏(我相信字符串会导致一些问题)。再加上您无法键入大多数其他引用的事实,并且尝试在 PHP 中强制进行静态类型(此时)没有太大价值。正因为如此,此时 PHP 中接口价值远低于强类型语言。它们具有可读性的好处,但没有其他好处。多重实现甚至没有好处,因为您仍然必须声明方法并在实现者中为它们提供主体。

于 2008-08-21T17:06:42.093 回答
3

我不知道其他语言,那里的接口概念是什么。但是对于 PHP,我会尽力解释它。请耐心等待,如果这有帮助,请发表评论。

接口作为“合同”工作,指定一组子类做什么,但不指定它们如何做。

规则

  1. 接口不能被实例化。

  2. 您不能在接口中实现任何方法,即它只包含方法的签名而不包含详细信息(主体)。

  3. 接口可以包含方法和/或常量,但不包含属性。接口常量与类常量具有相同的限制。接口方法是隐式抽象的。

  4. 接口不得声明构造函数或析构函数,因为这些是类级别的实现细节。

  5. 接口中的所有方法都必须具有公共可见性。

现在让我们举个例子。假设我们有两个玩具:一个是狗,另一个是猫。

正如我们所知,狗叫,猫叫。这两者具有相同的说话方法,但具有不同的功能或实现。假设我们给用户一个带有说话按钮的遥控器。

When the user presses speak button, the toy have to speak it doesn't matter if it's Dog or a Cat.

This a good case to use an interface, not an abstract class because the implementations are different. Why? Remember

If you need to support the child classes by adding some non-abstract method, you should use abstract classes. Otherwise, interfaces would be your choice.

于 2019-04-05T05:37:58.610 回答
2

以下是PHP接口的要点

  1. 它用于定义类中不需要的方法[如果要加载 html,则需要 id 和 name,因此在这种情况下,接口包括 setID 和 setName]。
  2. 接口严格强制类包含其中定义的所有方法。
  3. 您只能在具有公共可访问性的接口中定义方法。
  4. 您还可以像类一样扩展接口。您可以使用 extends 关键字在 php 中扩展接口。
  5. 扩展多个接口。
  6. 如果两个接口具有相同的名称,则不能实现 2 个接口。它会抛出错误。

示例代码:

interface test{
    public function A($i);
    public function B($j = 20);
}

class xyz implements test{
    public function A($a){
        echo "CLASS A Value is ".$a;
    }
    public function B($b){
        echo "CLASS B Value is ".$b;
    }
}
$x = new xyz();
echo $x->A(11);
echo "<br/>";
echo $x->B(10);
于 2017-01-12T06:39:51.317 回答
2

我们看到抽象类和接口的相似之处在于它们提供了必须在子类中实现的抽象方法。但是,它们仍然存在以下差异:

1.接口可以包含抽象方法和常量,但不能包含具体方法和变量。

2.接口中的所有方法都必须在公共可见范围内。

3.一个类可以实现多个接口,而只能继承一个抽象类。

                                  interface                      abstract class
the code                     - abstract methods               - abstract methods
                             - constants                      - constants                  
                                                              - concrete methods
                                                              - concrete variables

access modifiers             
                             - public                         - public
                                                              - protected
                                                              - private
                                                                etc.
number of parents          The same class can implement
                           more than 1 interface              The child class can 
                                                              inherit only from 1 abstract class

希望这将有助于任何人理解!

于 2018-02-16T11:17:08.727 回答
2

界面就像你的基因。

抽象类就像你真正的父母。

它们的目的是世袭的,但在抽象类与接口的情况下,继承的是更具体的。

于 2018-09-27T14:00:37.127 回答