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