【问题标题】:Interface or an Abstract Class: which one to use?接口或抽象类:使用哪一个?
【发布时间】:2010-12-21 08:26:35
【问题描述】:

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

如何将我的abstract class 更改为interface

【问题讨论】:

    标签: php oop interface abstract-class


    【解决方案1】:

    最佳实践是使用一个接口来指定契约和一个抽象类作为它的一个实现。该抽象类可以填充很多样板,因此您可以通过覆盖您需要或想要的内容来创建实现,而无需强制您使用特定的实现。

    【讨论】:

      【解决方案2】:

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

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

      【讨论】:

        【解决方案3】:

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

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

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

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

        【讨论】:

        • 我整天都在努力了解abstractinterface 类的用法,您的帖子已经说明了一切。非常感谢艾伦
        • 抽象类的另一个优点是能够定义抽象保护方法。并不总是有用,但可以在某些架构中派上用场。
        • 所以在很多情况下我们应该使用抽象类,因为它具有灵活性——这是我的结论:)
        • @volocuga :不一定,就像 Alan 指出的那样,只能扩展单个摘要。我个人不喜欢抽象实现接口的想法,因为它会导致代码混淆并且不太直接,IMO。
        【解决方案4】:

        Abstract ClassInterface 之间的区别:

        抽象类

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

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

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

        界面

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

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

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

        【讨论】:

        • 你能提供一个真实的例子来演示吗?
        • abstract class X implements Yclass X implements Y 有什么区别?
        • @Webinan 在abstract class X implements Y 中,您声明 X 的批量功能应该在派生类中实现,并且抽象类和派生类必须包含在 Y 中定义的函数,而class X implements Y 仅表示类 X 必须包含在 Y 中定义的函数。如果您的接口 Y 不打算由除 XI 之外的任何其他类实现,则实际上会跳过将 Y 定义为接口,而仅将 Y 中的函数实现为公共/protected/private 抽象函数,以确保它们在派生类中实现。
        • 接口不能only包含方法的定义,也可以包含constants
        • 喜欢你的比较。所以想补充一点。接口可以具有开箱即用的类常量,而抽象类则不能。
        【解决方案5】:

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

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

        【讨论】:

        • 嗨,这个答案可能由于它的格式化方式而被否决。如果它不是一个大代码块(四个空格组成一个代码块,取消缩进文本以将其从块中取出),并且如果这是从某个地方复制粘贴(看起来像这样),那就太好了称赞他们是有礼貌的。
        • 我爱你的水果榜样!自从我开始学习php以来,这个例子让我很清楚非常感谢
        • +1 What does that taste like? Well, it doesn't make much sense, so you shouldn't be able to do that. 现在我知道抽象了!
        • final 关键字有什么作用?很棒的帖子,谢谢。
        • @VineeshKalarickal 在 Person 示例中我不明白的是:1)使用抽象类 Person(如示例中所示); 2) 将 Person 编写为标准类并让 Employee 和 Student 覆盖 write_info() 方法。
        【解决方案6】:

        只是为了把它混在一起,但正如 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)
        

        【讨论】:

        • 多么棒的例子! ;)
        • @matt2000 不存在性别歧视,现在也适用于同性婚姻。伟大的编辑。 :)
        • 这是一个很棒的例子!但是,我认为您不能实例化一个抽象类,而是一个从抽象类扩展而来的类
        • 不会杀死 sudo 代码至少看起来像有问题的语言。应该有人在里面放一些美元。
        • 虽然这个例子很有趣,但我们不能直接实例化一个抽象类,请在例子中做一些修改,以免人们认为它在 PHP 中是有效的用法。
        【解决方案7】:

        从哲学的角度来看:

        • 抽象类表示“是”关系。假设我有水果,那么我会有一个水果抽象类,它具有共同的责任和共同的行为。

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

        从编码的角度来看:

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

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

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

        PS : "is a"/"should do" 是由 Vivek Vermani 的回答带来的,我并不是要窃取他的回答,只是为了重用这些术语,因为我喜欢它们!

        【讨论】:

        • 我相信你要找的词是可食用的。
        • 其实我相信它是一个“动词”,一个“做词”
        【解决方案8】:

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

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

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

        class Monitor{
            private int monitorNo;
        }
        

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

        interface Display{
            void display();
        }
        

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

        【讨论】:

        • PHP 没有定义返回类型,OP 用PHP 标记了这个问题
        【解决方案9】:

        添加一些已经很出色的答案:

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

        • 实现接口的任何类都承诺实现它定义的所有方法,否则必须声明为抽象。

        • 接口可以帮助管理这样一个事实,即与 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();
        }
        

        【讨论】:

          【解决方案10】:

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

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

          作为一个比喻,这可能会对你有所帮助:

          界面

          假设您用草莓烤了一种新型蛋糕,并​​制作了一个描述成分和步骤的食谱。 只有您知道为什么它的味道如此好并且您的客人喜欢它。 然后您决定发布您的食谱,以便其他人也可以尝试该蛋糕。

          重点是

          - 让它正确
          - 要小心
          - 防止可能会变坏的事情(比如草莓过多或其他东西)
          - 让尝试它的人更轻松
          -告诉你要做什么(比如搅拌)
          - 告诉您哪些可以做但不必做的事情

          这正是描述接口的内容。它是一个指南,一组观察食谱内容的说明。 就像您将在 PHP 中创建一个项目并且您想在 GitHub 上或与您的伙伴或其他任何地方提供代码一样。 界面是人们可以做的和你不应该做的。持有它的规则——如果你不遵守它,整个结构就会被破坏。


          抽象

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

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

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

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

          【讨论】:

          • 这是我最喜欢的 PHP 相关比较。这真的很有意义。谢谢!
          【解决方案11】:

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

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

          通过这种方式,我可以为不同的文件提供多个模板,并拥有一组明确区分的通用接口方法。该接口对访问方法给出了正确的类比,而不是与基本抽象类的类比。

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

          【讨论】:

            猜你喜欢
            • 1970-01-01
            • 1970-01-01
            • 2012-11-06
            • 1970-01-01
            • 2013-05-16
            • 2011-01-09
            • 2012-08-07
            • 2012-09-12
            • 2012-03-03
            相关资源
            最近更新 更多