Interface or an Abstract Class: which one to use?
请解释我什么时候应该使用php
如何将我的
当您想强制在您的系统(包括您自己)中工作的开发人员在将要构建的类上实现一组方法时,可以使用接口。
当您想要强制在您的系统(包括您自己)中工作的开发人员实现一组方法时,使用抽象类,并且您想要提供一些基础方法来帮助他们开发子类。
要记住的另一件事是客户端类只能扩展一个抽象类,而它们可以实现多个接口。所以,如果您在抽象类中定义行为契约,这意味着每个子类可能只符合一个契约。有时这是一件好事,当你想强迫你的用户程序员走一条特定的道路时。有时会很糟糕。想象一下,如果PHP的Countable和Iterator接口是抽象类而不是接口。
当您不确定要走哪条路(如下面的cletus所提到的)时,一种常见的方法是创建一个接口,然后让抽象类实现该接口。
抽象类
抽象类可以提供一些功能,其余的留给派生类。
派生类可以重写,也可以不重写在基类中定义的具体函数。
从抽象类扩展而来的子类在逻辑上应该是相关的。
界面
接口不能包含任何功能。它只包含方法的定义。
派生类必须为接口中定义的所有方法提供代码。
完全不同的和不相关的类可以使用接口逻辑地分组在一起。
为什么要使用抽象类?下面是一个简单的例子。假设我们有以下代码:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 | <?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 } } |
现在我给你一个苹果,你就吃了。味道怎么样?它尝起来像个苹果。
1 2 3 4 5 6 7 | <?php $apple = new Apple(); $apple->eat(); // Now I give you a fruit. $fruit = new Fruit(); $fruit->eat(); |
味道怎么样?嗯,这没什么意义,所以你不应该这样做。这是通过将水果类抽象化以及其中的eat方法来实现的。
1 2 3 4 5 6 7 8 9 10 11 | <?php abstract class Fruit { private $color; abstract public function eat(){} public function setColor($c) { $this->color = $c; } } ?> |
抽象类就像一个接口,但您可以在抽象类中定义方法,而在接口中,它们都是抽象的。抽象类可以同时具有空方法和工作/具体方法。在接口中,在那里定义的函数不能有主体。在抽象类中,它们可以。
一个现实世界的例子:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 | <?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"; } } 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"; } } ///---------- $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 |
最佳实践是使用一个接口来指定契约和一个抽象类作为它的一个实现。这个抽象类可以填充很多样板文件,这样您就可以创建一个实现,只需覆盖您需要或想要的内容,而不必强制使用特定的实现。
只是为了把它混合起来,但是正如克莱特斯所提到的,将接口与抽象类结合使用,我经常使用接口来澄清我的设计思想。
例如:
1 2 3 4 | <?php class parser implements parserDecoratorPattern { //... } |
这样,任何阅读我的代码的人(并且谁知道装饰器模式是什么)都会立刻知道a)我如何构建解析器,b)能够看到用于实现装饰器模式的方法。
另外,这里可能不是Java/C++/ETC程序员,但数据类型可以在这里发挥作用。您的对象是一种类型,当您通过编程方式将它们传递给类型时,它们就很重要。将可收缩项移动到接口中只指定方法返回的类型,而不指定实现它的类的基类型。
现在很晚了,我想不出一个更好的psudo代码示例,但下面是:
1 2 3 4 5 6 7 | <?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中的接口实际上没有真正的函数。它们仅用于语义和方案相关的原因。关键是要有一个尽可能灵活、可扩展和安全的项目来进行未来的扩展,不管开发人员以后是否有一个完全不同的使用计划。
如果您的英语不是本地语言,您可能会查找抽象和接口实际上是什么。寻找同义词。
这或许可以作为一种比喻:
界面
比方说,你用草莓做了一种新的蛋糕,然后你编了一份食谱,描述了配料和步骤。只有你知道为什么它的味道这么好,你的客人喜欢它。然后你决定公布你的食谱,这样其他人也可以尝尝这个蛋糕。
关键是
- to make it right
- to be careful
- to prevent things which could go bad (like too much strawberries or something)
- to keep it easy for the people who try it out
- to tell you how long is what to do (like stiring)
- to tell which things you CAN do but don't HAVE to
这正是描述接口的地方。它是一个指南,是一套观察配方内容的说明。就像你要用PHP创建一个项目,你想在Github上提供代码,或者与你的伙伴一起提供代码,或者其他什么。界面是人们可以做的,而你不应该做的。持有它的规则-如果你不服从其中一个,整个结构将被打破。
抽象化
在这里继续这个比喻…想象一下,这次你是来吃蛋糕的客人。那你现在就用食谱试着做蛋糕吧。但是你想要添加新的配料或者改变/跳过食谱中描述的步骤。接下来是什么?计划一个不同版本的蛋糕。这次是黑浆果,而不是稻草浆果和更多的香草奶油…美味。
这就是您可以考虑的原始蛋糕的扩展。你基本上通过创建一个新的配方来抽象它,因为它是一个小的不同。它有一些新的步骤和其他成分。然而,黑浆果版本有一些你从原来接管的部分-这些是每种蛋糕必须具备的基本步骤。就像牛奶一样的配料-这是每个衍生类都有的。
现在您要交换原料和步骤,这些必须在新版本的蛋糕中定义。这些是必须为新蛋糕定义的抽象方法,因为蛋糕中应该有水果,但是哪个呢?所以这次你吃黑浆果。完成。
在这里,你扩展了蛋糕,遵循界面,从中提取步骤和成分。
为了补充一些已经很好的答案:
抽象类允许您提供某种程度的实现,接口是纯模板。一个接口只能定义功能,不能实现它。
实现接口的任何类都会提交到实现它定义的所有方法,或者必须声明为抽象的。
接口可以帮助管理像Java一样,PHP不支持多重继承的事实。一个PHP类只能扩展一个父类。但是,您可以保证类可以实现任意多的接口。
类型:对于它实现的每个接口,类都采用相应的类型。因为任何类都可以实现一个接口(或多个接口),接口可以有效地连接在其他方面不相关的类型。
类既可以扩展超类,也可以实现任意数量的接口:
1
2
3class SubClass extends ParentClass implements Interface1, Interface2 {
// ...
}
Please explain when I should use an interface and when I should use abstract class?
当您只需要提供一个没有实现的模板时,请使用一个接口,并且您希望确保实现该接口的任何类将具有与实现该接口的任何其他类相同的方法(至少)。
当您想为其他对象(部分构建的类)创建基础时,使用抽象类。扩展抽象类的类将使用一些定义/实现的属性或方法:
1 2 3 4 5 6 7 | <?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". ?> |
How I can change my abstract class in to an interface?
这里是一个简化的案例/示例。删除所有实现细节。例如,将抽象类更改为:
1 2 3 4 5 | abstract class ClassToBuildUpon { public function doSomething() { echo 'Did something.'; } } |
到:
1 2 3 | interface ClassToBuildUpon { public function doSomething(); } |
从系统论的观点来看:
抽象类表示"是"关系。假设我有水果,那么我会有一个水果抽象类,共享共同的责任和共同的行为。
接口表示"应该做"的关系。在我看来,一个接口(初级开发人员的观点),应该通过一个动作或接近动作的东西来命名(对不起,找不到这个词,我不是英语母语者),我们说它是可实现的。你知道它可以吃,但你不知道你吃什么。
从编码的角度来看:
如果您的对象具有重复的代码,则表示它们具有共同的行为,这意味着您可能需要一个抽象类来重用代码,而这是您无法对接口执行的。
另一个区别是,一个对象可以实现您所需要的任意多个接口,但是由于"菱形问题",您只能有一个抽象类(请查看此处了解原因)。http://en.wikipedia.org/wiki/multiple-inheritance钻石问题)
我可能忘记了一些要点,但我希望它能澄清一些事情。
附言:"i s a"/"should do"是维韦克·维曼尼的答案,我无意窃取他的答案,只是为了重复使用这些术语,因为我喜欢它们!
抽象类和接口之间的技术差异已经在其他答案中精确列出。为了面向对象的编程,我想在编写代码时添加一个解释来在类和接口之间进行选择。
类应表示实体,而接口应表示行为。
让我们举个例子。计算机监视器是一个实体,应该表示为一个类。
1 2 3 | class Monitor{ private int monitorNo; } |
它的设计目的是为您提供一个显示界面,因此功能应该由一个界面定义。
1 2 3 | interface Display{ void display(); } |
还有许多其他的问题需要考虑,如其他答案中所解释的,但这是大多数人在编码时忽略的最基本的问题。
只是想添加一个示例,说明何时需要同时使用这两种方法。我目前正在编写一个绑定到通用ERP解决方案中的数据库模型的文件处理程序。
- 我有多个抽象类来处理标准CRUD,还有一些特殊的功能,比如针对不同类别的文件进行转换和流式处理。
- 文件访问接口定义了获取、存储和删除文件所需的一组通用方法。
通过这种方式,我可以为不同的文件使用多个模板,并使用一组具有明确区别的通用接口方法。接口对访问方法进行了正确的类比,而不是对基本抽象类进行了正确的类比。
接下来,当我将为不同的文件存储服务制作适配器时,这个实现将允许在完全不同的上下文中在其他地方使用接口。