接口或抽象类:使用哪个?


Answers:


458

当您要强制开发人员(包括您自己)在系统中工作以在他们要构建的类上实现一定数量的方法时,请使用接口。

当您要强制系统中的开发人员(包括您自己)执行一定数量的方法,并且要提供一些基本方法来帮助他们开发子类时,请使用抽象类。

要记住的另一件事是,客户端类只能扩展一个抽象类,而它们可以实现多个接口。因此,如果您要在抽象类中定义行为契约,则意味着每个子类只能遵循一个契约。有时候,当您想迫使用户程序员沿着特定的路径前进时,这是一件好事。有时会很糟糕。想象一下,如果PHP的Countable和Iterator接口是抽象类而不是接口。

当您不确定该走哪条路时(如下面cletus所述),一种常见的方法是创建一个接口,然后让您的抽象类实现该接口。


12
我整天都在努力了解abstractinterface类的用法,您的帖子对此一目了然。非常感谢Alan
afarazit 2011年

4
抽象类的另一个优点是可以定义受保护的抽象方法。并非总是有用,但在某些体系结构中可以派上用场。
netcoder 2011年

因此,在很多情况下,由于灵活性,我们应该使用抽象类-这是我的结论:)
ymakux 2014年

3
@volocuga:不一定像Alan所指出的那样,只能扩展一个摘要。我个人不喜欢抽象实现接口的想法,因为它会导致代码混淆,并且不太直接,IMO。
2014年

171

Abstract Class和之间的区别Interface

抽象类

抽象类可以提供一些功能其余的保留给派生类

  • 派生类可以覆盖也可以不覆盖基类中定义的具体功能。

  • 从抽象类扩展的子类在逻辑上应该是相关的。

接口

接口不能包含任何功能。它包含方法的定义。

  • 派生类必须为接口中定义的所有方法提供代码

  • 可以使用接口在逻辑上将完全不同且无关的类组合在一起。


1
您可以提供一个真实的例子进行演示吗?
RN库什瓦哈州

1
abstract class X implements Y和 之间有什么区别class X implements Y
Webinan'7

3
@Webinan在其中abstract class X implements Y声明X的批量功能应在派生类中实现,并且抽象类和派生类都必须包含Y定义的函数,而class X implements Y仅表示X类必须包含Y定义的函数。除了XI实际上将跳过将Y定义为接口,而仅将Y中的功能实现为公共/受保护/私有抽象功能以确保在派生类中实现这些功能之外,它不是由XI来实现的。
卡勒·斯特格斯特罗姆

1
接口不仅可以包含方法的定义,还可以包含常量
Thielicious

喜欢您的比较。因此想添加一些东西。接口可以直接使用类常量,而抽象类则不能。
诺曼·易卜拉欣

128

为什么要使用抽象类?以下是一个简单的示例。可以说我们有以下代码:

<?php 

class Fruit {
    private $color;

    public function eat() {
        // chew
    }

    public function setColor($c) {
        $this->color = $c;
    }
}

class Apple extends Fruit {
    public function eat() {
        // chew until core
    }
}

class Orange extends Fruit {
    public function eat() {
        // peeling
        // chew
    }
}

现在我给你一个苹果,你吃了。尝起来怎么样?尝起来像苹果。

<?php 
$apple = new Apple();
$apple->eat();

// Now I give you a fruit.
$fruit = new Fruit();
$fruit->eat();

那味道怎么样?好吧,这没有多大意义,所以您不应该能够做到这一点。这是通过将Fruit类及其内部的eat方法抽象化来实现的。

<?php 
abstract class Fruit {
    private $color;

    abstract public function eat(){}

    public function setColor($c) {
        $this->color = $c;
    }
}
?>

抽象类就像接口一样,但是您可以在抽象类中定义方法,而在接口中它们都是抽象的。抽象类可以同时具有空方法和工作/具体方法。在接口中,在那里定义的函数不能有主体。在抽象类中,它们可以。

一个真实的例子:

<?php 
abstract class person {

    public $LastName;
    public $FirstName;
    public $BirthDate;

    abstract protected function write_info();
}

final class employee extends person{

    public $EmployeeNumber;
    public $DateHired;

    public function write_info(){
        //sql codes here
        echo "Writing ". $this->LastName . "'s info to emloyee dbase table <br>";   
    }
}

final class student extends person{

    public $StudentNumber;
    public $CourseName;

    public function write_info(){
        //sql codes here
        echo "Writing ". $this->LastName . "'s info to student dbase table <br>";
    }
}

///----------
$personA = new employee;
$personB = new student;

$personA->FirstName="Joe";
$personA->LastName="Sbody";

$personB->FirstName="Ben";
$personB->LastName="Dover";

$personA->write_info();
// Writing Sbody's info to emloyee dbase table
$personB->write_info();
// Writing Dover's info to student dbase table 

2
嗨,这个答案可能由于其格式化方式而被否决了。如果它不是一个大的代码块(将四个空格放入代码块中,将文本缩进以将其从该块中取出),并且从某个地方复制粘贴(这看起来是这样),那会很好。礼貌地归功于他们。
卡米洛·马丁

9
我爱你,以水果为榜样!自从我开始学习php以来,这个例子使我非常感谢
Raheel

23
+1 What does that taste like? Well, it doesn't make much sense, so you shouldn't be able to do that.现在我知道摘要!
Webinan'7

什么是final关键词呢?很棒的帖子,谢谢。
Gus

1
@VineeshKalarickal我在Person示例中看不懂的区别是:1)使用抽象类Person(如示例中);2)将Person编写为标准类,并使Employee和Student覆盖write_info()方法。
Ferex '16

66

最佳实践是使用接口来指定合同和抽象类作为其一种实现。该抽象类可以填充很多样板,因此您可以通过覆盖需要或想要的内容来创建实现,而不必强迫您使用特定的实现。


37

只是将其混为一谈,但是正如Cletus提到的将接口与抽象类结合使用时,我经常使用该接口来阐明我的设计思想。

例如:

<?php
class parser implements parserDecoratorPattern {
    //...
}

这样,任何阅读我的代码的人(并且知道装饰器模式是什么)都将立即知道a)我如何构建解析器和b)能够看到用于实现装饰器模式的方法。

此外,在这里我可能不是一名Java / C ++ / etc程序员,但我可能已经脱离基础了,但是数据类型可以在这里起作用。您的对象属于一种类型,当您将它们传递给类型时,会以编程方式起作用。将可收缩项移入接口仅指示方法返回的类型,而不指示实现该方法的类的基本类型。

已经很晚了,我想不出一个更好的伪代码示例,但是这里有:

<?php
interface TelevisionControls {};
class Remote implements TelevisionControls {};
class Spouse implements TelevisionControls {};
Spouse spouse = new Spouse();
Remote remote = new Remote();
isSameType = (bool)(remote == spouse)

1
多么棒的例子!;)
Joel Murphy

@ matt2000现在不性别歧视,并且也适用于同性婚姻。很棒的编辑。:)
奥斯汀·霍根

4
这是一个了不起的例子!但是,我认为您不能实例化一个抽象类,而是可以实例化一个抽象类的扩展类
Arielle Nguyen 2015年

2
不会使sudo代码至少看起来像所讨论的语言。有人应该在里面放一些美元。
我曾经摔过一只熊。

2
尽管该示例很有趣,但是我们无法直接实例化一个抽象类,请对示例进行更改,以使人们可能不会将其视为PHP中的有效用法。
saji89

16

主要区别在于抽象类可以包含默认实现,而接口则不能。

接口是行为契约,没有任何实现。


16

另外,只想在这里补充一点,仅仅是因为任何其他OO语言都具有某种接口和抽象,也不意味着它们具有与PHP中相同的含义和目的。抽象/接口的使用略有不同,而PHP中的接口实际上没有真正的功能。它们仅出于语义和与方案相关的原因而使用。关键是要有一个尽可能灵活,可扩展且对将来的扩展安全的项目,无论以后的开发人员是否有完全不同的使用计划。

如果您的英语不是母语,则可能会查找“抽象和接口”实际上是什么。并寻找同义词。

作为一个隐喻,这可能会帮助您:

接口

假设您用草莓烘烤了一种新的蛋糕,并制作了描述其成分和步骤的食谱。只有您知道为什么品尝得这么好,您的客人喜欢它。然后,您决定发布食谱,以便其他人也可以尝试该蛋糕。

这里的重点是

-正确处理
-小心
-防止可能变质的事情(如草莓或其他过多的东西)
-方便尝试的人
-告诉您要做多长时间(例如搅拌) )
-告诉您您可以执行哪些操作而不必执行

正是此描述了接口。它是一个指南,是一组观察配方内容的说明。就像您将要使用PHP创建一个项目,并且想要在GitHub上或与您的伴侣或其他任何人一起提供代码一样。界面是人们可以做什么,而你不应该做什么。持有它的规则-如果您不遵守,则会破坏整个结构。


摘要

继续这里的隐喻...想象一下,您这次是客人,要吃那个蛋糕。然后,您现在就使用食谱尝试该蛋糕。但是您想添加新成分或更改/跳过配方中描述的步骤。那么接下来呢?计划该蛋糕的其他版本。这次是黑浆果而不是稻草浆果,还有更多香草奶油...好吃。

这就是您可以考虑扩展的原始蛋糕。基本上,您可以通过创建新配方来对其进行抽象,因为它与众不同。它具有一些新步骤和其他要素。但是,黑莓版本有一些是您从原始版本接手的部分-这些是每种蛋糕必须具备的基本步骤。就像牛奶一样的成分-每个派生阶层都有的。

现在,您想交换成分和步骤,这些必须在新版蛋糕中定义。这些是必须为新蛋糕定义的抽象方法,因为蛋糕中应该有水果,但是哪个呢?因此,这次您取黑莓。做完了

在这里,您已经扩展了蛋糕,遵循了界面并从中提取了步骤和成分。


1
这是我最喜欢的任何与PHP相关的比较。这真的很有意义。谢谢!
cbloss793 '19

13

要添加一些已经很好的答案:

  • 抽象类让您提供一定程度的实现,接口是纯模板。一个接口只能定义功能,而不能实现它。

  • 任何实现该接口的类都将致力于实现其定义的所有方法,或者必须将其声明为抽象的。

  • 接口可以帮助管理以下事实:与Java一样,PHP不支持多重继承。PHP类只能扩展单个父级。但是,您可以使一个类承诺实现所需的任意数量的接口。

  • 类型:对于它实现的每个接口,类都具有相应的类型。因为任何类都可以实现一个接口(或多个接口),所以接口有效地连接了原本不相关的类型。

  • 一个类可以扩展一个超类并实现任何数量的接口:

    class SubClass extends ParentClass implements Interface1, Interface2 {
        // ...
    }

请解释什么时候应该使用接口以及什么时候应该使用抽象类?

当您只需要提供一个没有实现的模板时,可以使用一个接口,并且要确保实现该接口的任何类都具有与实现该接口的任何其他类相同的方法(至少)。

当您要为其他对象创建基础时(部分构建的类),请使用抽象类。扩展您的抽象类的类将使用一些定义/实现的属性或方法:

<?php
// interface
class X implements Y { } // this is saying that "X" agrees to speak language "Y" with your code.

// abstract class
class X extends Y { } // this is saying that "X" is going to complete the partial class "Y".
?>

如何将抽象类更改为接口?

这是一个简化的案例/示例。取出所有实施细节。例如,将您的抽象类更改为:

abstract class ClassToBuildUpon {
    public function doSomething() {
          echo 'Did something.';
    }
}

至:

interface ClassToBuildUpon {
    public function doSomething();
}

12

从系统角度看:

  • 抽象类表示“是”关系。假设我有水果,那么我将有一个Fruits抽象类,它具有共同的责任和共同的行为。

  • 接口表示“应做”关系。以我的观点(一个初级开发人员的观点),一个接口应该用一个动作或一个接近动作的名称来命名(对不起,找不到这个词,我不是英语为母语的人)可以说是IEatable。您知道它可以食用,但您不知道吃什么。

从编码的角度来看:

  • 如果您的对象具有重复的代码,则表明它们具有共同的行为,这意味着您可能需要抽象类来重用代码,而接口不能这样做。

  • 另一个区别是,一个对象可以实现所需的任意数量的接口,但是由于“钻石问题”,您只能拥有一个抽象类(请查看此处以了解原因!http://en.wikipedia.org/wiki/ Multiple_inheritance#The_diamond_problem

我可能会忘记一些要点,但我希望它可以澄清一些问题。

PS:Vivek Vermani的答案带有“是” /“应该做”的意思,我并不是要窃取他的答案,只是要重复使用这些术语,因为我喜欢它们!


2
我相信您要寻找的字是可食用的。
特拉维斯·韦斯顿2014年

1
实际上,我相信它是一个“动词”,一个“在做的单词”
Grizly

7

在其他答案中已经精确列出了抽象类和接口之间的技术差异。我想添加一个解释,以便在编写代码时针对面向对象的编程在类和接口之间进行选择。

一个类应该代表一个实体,而一个接口应该代表行为。

让我们举个例子。计算机监视器是一个实体,应表示为类。

class Monitor{
    private int monitorNo;
}

它旨在为您提供显示界面,因此应通过界面定义功能。

interface Display{
    void display();
}

正如其他答案中所解释的,还有许多其他事情要考虑,但这是大多数人在编码时忽略的最基本的事情。


2
PHP犯规定义返回类型和OP标记这个问题有PHP
Purefan

1

只是想添加一个示例,说明何时可能需要同时使用两者。我目前正在编写通用ERP解决方案中绑定到数据库模型的文件处理程序。

  • 我有多个抽象类,它们处理标准的crud以及一些特殊功能,例如针对不同类别的文件的转换和流传输。
  • 文件访问接口定义了获取,存储和删除文件所需的一组通用方法。

这样,我就可以拥有用于不同文件的多个模板和一组具有明显区别的通用接口方法。该接口为访问方法提供了正确的类比,而不是基本抽象类所具有的类比。

当我为不同的文件存储服务制作适配器时,此实现将允许该接口在完全不同的上下文中使用。

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.