接口还是抽象类:使用哪一个?

请解释什么时候我应该使用PHP interface,什么时候我应该使用abstract class?

如何将我的abstract class改为interface?

133225 次浏览

最佳实践是使用接口来指定契约,并将抽象类作为契约的一个实现。该抽象类可以填充大量样板文件,因此您可以通过重写需要或想要的内容来创建实现,而不必强制使用特定的实现。

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

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

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

当你想强迫在你的系统中工作的开发人员(包括你自己)实现一组方法而且时,使用抽象类,你想提供一些基方法来帮助他们开发他们的子类。

另一件需要记住的事情是客户端类只能扩展一个抽象类,而它们可以实现多个接口。因此,如果您在抽象类中定义行为契约,这意味着每个子类可能只符合一个契约。有时这是一件好事,当你想强迫你的用户程序员沿着特定的路径。其他时候就不好了。想象一下,如果PHP的Countable和Iterator接口是抽象类而不是接口。

当你不确定该走哪条路时(正如克里特斯下面所提到的),一种常见的方法是创建一个接口,然后让你的抽象类实现该接口。

Abstract ClassInterface的区别:

抽象类

抽象类可以提供一些功能剩下的留给派生类

  • 派生类可以重写基类中定义的具体函数,也可以不重写。

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

接口

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

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

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

为什么要使用抽象类?下面是一个简单的例子。假设我们有以下代码:

<?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

只是把这一点加入到混合中,但正如Cletus提到的将接口与抽象类结合使用一样,我经常使用接口来阐明我的设计思维。

例如:

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

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

此外,我可能不是Java/ c++ /等程序员,但数据类型可以在这里发挥作用。你的对象是一个类型,当你传递它们时,类型在编程上很重要。将可收缩项移动到接口中只指定方法返回的类型,但不指定实现它的类的基类型。

现在已经很晚了,我想不出一个更好的伪代码示例,但如下所示:

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

从哲学的角度来看:

  • 抽象类表示“is a”关系。假设我有水果,我有一个水果抽象类,它有共同的职责和共同的行为。

  • 接口代表“应该做”的关系。一个界面,在我看来(这是一个初级开发人员的观点),应该由一个动作来命名,或者类似于一个动作的东西(对不起,找不到这个词,我不是英语母语),比如说IEatable。你知道它可以吃,但你不知道你吃了什么。

从编码的角度来看:

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

  • 另一个区别是,一个对象可以实现任意多的接口,但是由于“菱形问题”,您只能有一个抽象类(查看这里了解原因!< a href = " http://en.wikipedia.org/wiki/Multiple_inheritance The_diamond_problem " > http://en.wikipedia.org/wiki/Multiple_inheritance The_diamond_problem < / >)

我可能忘记了一些要点,但我希望它能澄清一些事情。

PS:“是一个”/“应该做”是由Vivek Vermani的答案带来的,我不是故意偷他的答案,只是重复使用这些术语,因为我喜欢它们!

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

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

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

class Monitor{
private int monitorNo;
}

它的设计目的是为您提供一个显示界面,因此功能应该由接口定义。

interface Display{
void display();
}

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

再加上一些已经很好的答案:

  • 抽象类让你提供某种程度的实现,接口是纯粹的模板。一个接口只能定义功能,它永远不能实现它。

  • 实现该接口的任何类都提交实现其定义的所有方法,否则必须声明为抽象类。

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

  • 类型:对于它实现的每个接口,类采用相应的类型。因为任何类都可以实现一个接口(或多个接口),

  • .接口可以有效地连接不相关的类型
  • 一个类既可以扩展超类,也可以实现任意数量的接口:

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

Please explain when I should use an interface and when I should use abstract class?

Use an interface when you need to provide only a template with no implementation what so ever, and you want to make sure any class that implements that interface will have the same methods as any other class that implements it (at least).

Use an abstract class when you want to create a foundation for other objects (a partially built class). The class that extends your abstract class will use some properties or methods defined/implemented:

<?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();
}

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

如果你的英语不是母语,你可以查一下抽象和接口到底是什么。还要注意同义词。

这也许可以作为一个比喻:

接口

比方说,你用草莓烤了一种新的蛋糕,你还写了一份食谱,描述了配料和步骤。 只有你自己知道为什么它的味道这么好,你的客人喜欢它。 然后你决定公布你的食谱,这样其他人也可以尝试你的蛋糕

这里的重点是 < br >

- to make it right
-小心
-防止事情变坏(比如草莓太多之类的)
-让尝试的人容易上手
-告诉你要做什么(比如搅拌)
-告诉哪些事情你可以做,但不必

这是描述接口的。它是一份指南,一套遵守食谱内容的说明。 就像你要用PHP创建一个项目,你想把代码提供给GitHub或你的伙伴或其他什么。 界面是指人们可以做什么,你不应该做什么。持有它的规则-如果你不遵守一个,整个结构将被打破

< br >

抽象

继续这个比喻…想象一下,这次你是吃蛋糕的客人。那你现在就用这个食谱做蛋糕。 但你想添加新的食材或更改/跳过食谱中描述的步骤。那么接下来会发生什么?计划一个不同版本的蛋糕。 这次用的是黑莓而不是草莓,还有更多的香草奶油…美味

这是你可以考虑的原始蛋糕的扩展。你基本上是通过创建一个新食谱来对它进行抽象,因为它有点不同。它有一些新的步骤和其他成分。然而,黑莓蛋糕的一些部分是你从原来的蛋糕中继承来的——这些是每种蛋糕都必须具备的基本步骤。就像牛奶一样的成分-这是每个派生类都有的。

现在你想要交换原料和步骤,这些必须在新版本的蛋糕中定义。这些是抽象方法,必须为新蛋糕定义,因为蛋糕中应该有一个水果,但哪个?所以这次你选黑莓。完成了。

好了,你已经扩展了蛋糕,遵循了界面,并从中提取了步骤和成分。

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

  • 我有多个抽象类,处理标准的crud,也有一些特殊的功能,如转换和流的不同类别的文件。
  • 文件访问接口定义了一组用于获取、存储和删除文件的通用方法。

通过这种方式,我可以为不同的文件拥有多个模板,以及一组具有明显区别的公共接口方法。接口对访问方法进行了正确的类比,而不是对基抽象类的类比。

接下来,当我将为不同的文件存储服务制作适配器时,这种实现将允许在完全不同的上下文中在其他地方使用接口。