请解释什么时候应该使用 PHP interface
以及什么时候应该使用 abstract class
?
如何将我的 abstract class
更改为 interface
?
当您想强制在您的系统中工作的开发人员(包括您自己)在他们将要构建的类上实现一组方法时,请使用接口。
当您想要强制在您的系统中工作的开发人员(包括您自己)实现一组方法并且您想要提供一些帮助他们开发子类的基本方法时,请使用抽象类。
要记住的另一件事是客户端类只能扩展一个抽象类,而它们可以实现多个接口。所以,如果你在抽象类中定义你的行为契约,这意味着每个子类可能只符合一个契约。有时这是一件好事,当您想强迫您的用户程序员沿着特定的路径前进时。其他时候会很糟糕。想象一下,如果 PHP 的 Countable 和 Iterator 接口是抽象类而不是接口。
当您不确定要采用哪种方法时(如 cletus below 所述),一种常见的方法是创建一个接口,然后让您的抽象类实现该接口。
Abstract Class
和 Interface
之间的区别:
抽象类
抽象类可以提供一些功能,其余的留给派生类。
派生类可能会或可能不会覆盖基类中定义的具体函数。
从抽象类扩展的子类在逻辑上应该是相关的。
界面
接口不能包含任何功能。它只包含方法的定义。
派生类必须为接口中定义的所有方法提供代码。
完全不同且不相关的类可以使用接口在逻辑上组合在一起。
abstract class X implements Y
和 class X implements Y
有什么区别?
abstract class X implements Y
中,您声明 X 的大部分功能应在派生类中实现,并且抽象类和派生类 必须 包含在 Y 中定义的函数,而 class X implements Y
仅表示类 X 必须包含在 Y 中定义的函数。如果您的接口 Y 不打算由除 XI 之外的任何其他类实现,则实际上会跳过将 Y 定义为接口,而仅将 Y 中的函数实现为公共/受保护/私有抽象函数以使确保它们在派生类中实现。
为什么要使用抽象类?下面是一个简单的例子。假设我们有以下代码:
<?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
What does that taste like? Well, it doesn't make much sense, so you shouldn't be able to do that.
现在我知道抽象了!
final
关键字有什么作用?很棒的帖子,谢谢。
最佳实践是使用一个接口来指定契约和一个抽象类作为它的一个实现。该抽象类可以填充很多样板文件,因此您可以通过覆盖您需要或想要的内容来创建实现,而无需强制您使用特定的实现。
只是为了把它混在一起,但正如 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)
另外,只是想在这里补充一点,仅仅因为任何其他 OO 语言也具有某种接口和抽象,并不意味着它们具有与 PHP 相同的含义和目的。抽象/接口的使用略有不同,而 PHP 中的接口实际上并没有真正的功能。它们仅用于语义和方案相关的原因。关键是要让项目尽可能灵活、可扩展且对未来的扩展安全,无论开发人员以后是否有完全不同的使用计划。
如果您的英语不是母语,您可能会查找抽象和接口实际上是什么。并寻找同义词。
作为一个比喻,这可能会对您有所帮助:
界面
比方说,你用草莓烤了一种新的蛋糕,你制作了一个描述配料和步骤的食谱。只有您知道为什么它的味道如此好并且您的客人喜欢它。然后您决定发布您的食谱,以便其他人也可以尝试该蛋糕。
这里的重点是
做对 - 小心 - 防止可能变坏的事情(比如太多草莓或其他东西) - 让尝试的人轻松 - 告诉你要做什么多长时间(比如搅拌) - 告诉你可以做但不必做的事情
这正是描述接口的内容。它是一个指南,一组观察食谱内容的说明。就像您将在 PHP 中创建一个项目并且您想在 GitHub 上或与您的伙伴或其他任何地方提供代码一样。界面是人们可以做的和你不应该做的。持有它的规则 - 如果你不遵守一个,整个构造将被破坏。
抽象
在这里继续这个比喻……想象一下,这次你是吃蛋糕的客人。那你现在就用这个食谱试试那个蛋糕。但是您想添加新成分或更改/跳过食谱中描述的步骤。那么接下来会发生什么?计划一个不同版本的蛋糕。这次是黑莓而不是草莓和更多的香草奶油……很好吃。
这是您可以考虑的原始蛋糕的扩展。您基本上通过创建一个新配方来对其进行抽象,因为它有点不同。它有一些新的步骤和其他成分。然而,黑莓版本有一些你从原版中接管的部分——这些是每种蛋糕必须具备的基本步骤。就像牛奶一样的成分 - 这是每个派生类都有的。
现在你想交换配料和步骤,这些必须在新版本的蛋糕中定义。这些是必须为新蛋糕定义的抽象方法,因为蛋糕中应该有一个水果,但是哪个?所以这次你吃黑莓。完毕。
好了,你已经扩展了蛋糕,遵循了界面并从中提取了步骤和成分。
主要区别是抽象类可以包含默认实现,而接口不能。
接口是没有任何实现的行为契约。
添加一些已经很好的答案:
抽象类让你提供某种程度的实现,接口是纯模板。接口只能定义功能,它永远无法实现它。
任何实现该接口的类都承诺实现它定义的所有方法,或者必须将其声明为抽象的。
接口可以帮助管理 PHP 不支持多重继承这一事实,就像 Java 一样。 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();
}
从哲学的角度来看:
抽象类表示“是”关系。假设我有水果,那么我会有一个水果抽象类,它具有共同的责任和共同的行为。
一个接口代表一种“应该做”的关系。一个界面,在我看来(这是一个初级开发者的意见),应该用一个动作来命名,或者接近一个动作的东西,(对不起,找不到这个词,我不是英语母语者)让我们说 IEatable。你知道它可以吃,但你不知道你吃什么。
从编码的角度来看:
如果您的对象具有重复的代码,则表明它们具有共同的行为,这意味着您可能需要一个抽象类来重用代码,而您不能使用接口来做到这一点。
另一个区别是,一个对象可以实现任意数量的接口,但由于“钻石问题”,您只能拥有一个抽象类(查看此处了解原因!http://en.wikipedia.org/wiki/ Multiple_inheritance#The_diamond_problem)
我可能忘记了一些要点,但我希望它可以澄清一些事情。
PS:“是”/“应该做”是由Vivek Vermani的回答带来的,我并不是要窃取他的回答,只是为了重用这些术语,因为我喜欢它们!
抽象类和接口之间的技术差异已经在其他答案中准确列出。为了面向对象编程,我想在编写代码时添加一个解释以在类和接口之间进行选择。
一个类应该代表一个实体,而一个接口应该代表行为。
让我们举个例子。计算机监视器是一个实体,应该表示为一个类。
class Monitor{
private int monitorNo;
}
它旨在为您提供显示界面,因此功能应由界面定义。
interface Display{
void display();
}
正如其他答案中所解释的,还有许多其他事情需要考虑,但这是大多数人在编码时忽略的最基本的事情。
PHP
标记了这个问题
只是想添加一个示例,说明您何时可能需要同时使用两者。我目前正在编写一个绑定到通用 ERP 解决方案中的数据库模型的文件处理程序。
我有多个抽象类来处理标准的 crud 以及一些特殊的功能,比如不同类别文件的转换和流式传输。
文件访问接口定义了获取、存储和删除文件所需的一组通用方法。
这样,我就可以为不同的文件拥有多个模板和一组具有明显区别的通用接口方法。接口给出了访问方法的正确类比,而不是基本抽象类的类比。
当我为不同的文件存储服务制作适配器时,这个实现将允许接口在完全不同的上下文中的其他地方使用。
不定期副业成功案例分享
abstract
和interface
类的用法,你的帖子说得很清楚。非常感谢艾伦