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

时间:2009-11-29 06:21:02

标签: php oop interface abstract-class

请解释我何时应该使用PHP interface以及何时使用abstract class

如何将abstract class更改为interface

11 个答案:

答案 0 :(得分:428)

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

当您想强制在您的系统中工作的开发人员(包括您自己)实施一系列方法时,请使用抽象类您想提供一些有助于他们培养孩子的基本方法类。

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

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

答案 1 :(得分:151)

Abstract ClassInterface之间的差异:

抽象类

抽象类可以提供某些功能将其余功能留给派生类

  • 派生类可以覆盖或不覆盖基类中定义的具体函数。

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

<强>接口

界面 不能包含任何功能 包含方法的定义。

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

  • 完全不同的和不相关的类可以使用界面在逻辑上组合在一起。

答案 2 :(得分:117)

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

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

答案 3 :(得分:62)

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

答案 4 :(得分:37)

只是把它放到混合中,但正如Cletus提到的将接口与抽象类结合使用时,我经常使用界面来阐明我的设计思维。

例如:

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

这样,任何阅读我的代码的人(以及谁知道Decorator模式是什么)都会马上知道a)我如何构建我的解析器和b)能够看到用于实现装饰器模式的方法。

此外,我可能不在这里,不是Java / C ++ / etc程序员,但数据类型可以在这里发挥作用。您的对象属于某种类型,当您以类型方式传递它们时会以编程方式进行操作。将可契约项移动到接口中只会指示方法返回的类型,而不是实现它的类的基类型。

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

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

答案 5 :(得分:13)

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

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

答案 6 :(得分:12)

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

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

这可能会帮助你作为一个比喻:

<强>接口

让我们说,你用草莓烤一种新蛋糕,然后你制作了描述成分和步骤的食谱。 只有你知道为什么它的品尝如此之好,你的客人喜欢它。 然后你决定发布你的食谱,以便其他人也可以尝试那个蛋糕。

这里的重点是

  

- 使其成为正确      - 小心点      - 防止可能变坏的东西(如草莓或其他东西太多)
     - 让试用它的人很容易      - 告诉你要做多久(如搅拌)
     - 告诉你可以做哪些事情,但不要

这正是描述接口的原因。它是一个指南,一组观察食谱内容的指令。 就像你在PHP中创建一个项目一样,你想在GitHub或你的配偶或其他什么提供代码。 界面是人们可以做的,不应该做的。持有它的规则 - 如果你不遵守它,整个结构将被打破。


<强> ABSTRACTION

在这里继续这个比喻......想象一下,这次你是客人吃那块蛋糕。然后你现在正在尝试使用食谱的蛋糕。 但是您想要添加新成分或更改/跳过配方中描述的步骤。接下来会发生什么?计划该蛋糕的不同版本。 这次是用黑浆果而不是草莓浆和更多的香草奶油......好吃。

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

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

你去了,你已经扩展了蛋糕,接着是界面,并从中抽取了步骤和成分。

答案 7 :(得分:10)

从哲学的角度来看:

  • 抽象类表示“是一种”关系。让我说我有水果,我会有一个水果抽象类,它有共同的责任和共同的行为。

  • 界面代表“应该做”的关系。在我看来,一个界面(这是一个初级开发者的意见),应该通过一个动作命名,或者一个接近一个动作的东西(抱歉,找不到这个词,我不是英语为母语的人)让我们说IEatable。你知道它可以吃,但你不知道你吃了什么。

从编码的角度来看:

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

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

我可能会忘记一些观点,但我希望它可以澄清一些事情。

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

答案 8 :(得分:10)

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

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

  • 任何实现接口的类都会提交实现它定义的所有方法,或者必须将其声明为abstract。

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

  • type:对于它实现的每个接口,该类采用相应的类型。因为任何类都可以实现接口(或更多接口), 接口有效地连接不相关的类型。

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

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

答案 9 :(得分:5)

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

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

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

class Monitor{
    private int monitorNo;
}

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

interface Display{
    void display();
}

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

答案 10 :(得分:1)

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

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

通过这种方式,我可以获得不同文件的多个模板和一组明确区别的通用接口方法。该接口给出了访问方法的正确类比,而不是基本抽象类的内容。

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