【问题标题】:PHP: what's the reason some people "fake" abstract methods by exclusively throwing an exception in the body?PHP:某些人通过在主体中专门抛出异常来“伪造”抽象方法的原因是什么?
【发布时间】:2019-06-11 08:49:17
【问题描述】:

最近经常看到如下代码(这个例子取自 Symfony):

protected function execute(InputInterface $input, OutputInterface $output)
{
    throw new LogicException('You must override the execute() method in the concrete command class.');
}

与仅将方法标记为 abstract 相比,这里有什么好处?

对...有什么好处

  1. ...库作者?
  2. ...图书馆用户?

我已经为 Java (Faking abstract methods using exceptions in Java) 找到了一个类似的问题,但它的答案不是很有帮助,因为它的答案是明确的猜测和意见。

【问题讨论】:

标签: php symfony exception abstract


【解决方案1】:

只有抽象类可以有抽象方法。

您不能从抽象类创建对象。

所有抽象方法都必须在非抽象类中实现。

构造示例:

想象一下:

abstract class Repository {
    public abstract function read();
    public abstract function write($object);
    public abstract function delete($object);
    public function connection() {
        //this is implemented for you by the framework so you don't have to do it every time
    }

现在您想要实现一个存储库来记录用户操作(登录、注销等)

您不希望删除这些条目中的任何一个,而此时您不想实现 delete 函数。但是您必须这样做,因为您的 UserActionRepository 不是抽象的,因为您实际上需要它的实例。那是你抛出异常的时候。

【讨论】:

  • 为什么要实例化一个缺少实现的类?就像可以实例化一个接口一样。
  • 当您不需要实现时,因为您可以确定您不需要该方法。我会尝试提出一个有用/真实世界的例子
  • 谢谢,我真的很想看到一个令人信服的例子。因为对于没有示例的论点,我会回答“如果您不需要方法,请不要声明它”。或“如果您不希望课程被扩展,请将其标记为最终课程”。或者“如果您必须扩展并且必须有一个已实现的方法,请将其标记为抽象”。 “类可以选择性地扩展,但如果它被扩展,它必须实现一个方法”这种情况闻起来像糟糕的架构,因为基类显然不需要这个方法——那么为什么它首先在基类中?
  • 然后在派生类中保持delete方法为空即可。在良好的架构中,抽象方法已经在 base(!) 类中调用。您解释的是接口的用例(DeletableInterface、WritableInterface 等)
  • @Foobar 没有抽象方法的工作。您必须实施它们,否则您将被抛出一个致命错误。您正在抛出异常以确保开发人员不应调用此方法。
【解决方案2】:

在这种特殊情况下,cmets 解释说:

/**
 * Executes the current command.
 *
 * This method is not abstract because you can use this class
 * as a concrete class. In this case, instead of defining the
 * execute() method, you set the code to execute by passing
 * a Closure to the setCode() method.
 *
 * @return int|null null or 0 if everything went fine, or an error code
 *
 * @throws LogicException When this abstract method is not implemented
 *
 * @see setCode()
 */
protected function execute(InputInterface $input, OutputInterface $output)
{
    throw new LogicException('You must override the execute() method in the concrete command class.');
}

您可以对整体设计提出异议,因为它可能有点老套,但在实践中效果很好。查看 Command::run 以了解使用闭包或执行的决定是在哪里做出的。至少可以说是一个小众案例。

我知道在另一个答案的 cmets 中讨论了很多这个问题,但我认为这可能有助于总结。我还快速搜索了 Symfony 框架代码,看看在哪里使用了闭包方法。没有找到任何东西。闭包支持可以追溯到最初的 2.0 版本,因此它可能具有“当时似乎是个好主意”的一些功能。

【讨论】:

    【解决方案3】:

    一个可能的用例是可选方法。如果你把它抽象化,那么所有的子类都需要实现它:

    abstract class Database
    {
        abstract public function export();
    }
    
    class MySQL extends Database
    {
    }
    

    致命错误:MySQL 类包含 1 个抽象方法,因此必须声明为抽象方法或实现其余方法 (Database::export)

    如果你创建一个常规方法,那么子类只需要在他们计划支持的情况下实现该方法。

    abstract class Database
    {
        public function export(){
            throw new \LogicException(__CLASS__ . ' driver does not support ' . __FUNCTION__);
        }
    }
    
    class MySQL extends Database
    {
    }
    

    ...但是如果你尝试使用它,你会得到一个很好的错误:

    $s = new MySQL();
    $s->export();
    

    致命错误:未捕获的异常“LogicException”:MySQL 驱动程序不支持导出

    【讨论】:

    • 但这不是使用接口的理由吗?
    • @Foobar Fatal error: Interface function Database::export() cannot contain body。不确定我是否遗漏了什么。
    • 我的意思是,如果你不想“可导出”,就不要实现interface Exportable。因此,首先不存在“不受支持”的方法。 class MySQL extends Database implements Exportable 支持导出,implements Exportable 不支持。
    • 这种技术很好,但它不能扩展,因为一个类只能实现零个或一个接口。我想你可以创建特征。然而,那里有大量的 pre-PHP/5.4 代码,这通常解释了许多奇怪的解决方法。
    • 一个类可以实现任意数量的接口(好吧,从技术上讲,我假设一个整数在架构上可以容纳多少亿)。它只能从一个类继承(extend 每个类只能使用一次,但implements 可以容纳任意数量)。而且 PHP 5.4 的说法并不成立,因为我在年轻的时候才开始看到这种“伪抽象”模式。
    猜你喜欢
    • 1970-01-01
    • 2013-03-18
    • 1970-01-01
    • 2016-04-28
    • 1970-01-01
    • 1970-01-01
    • 2021-05-28
    • 1970-01-01
    • 2018-08-11
    相关资源
    最近更新 更多