被抽象PHP窥探


Snookered by abstract - PHP

我继承了一个抽象类:

abstract class Test
{
    public function GetTests()
    {
    }
}

我有一个我大部分时间都在使用抽象类实现的具体例子:

class Concrete extends Test
{
    // No problemmos
}

我最近不得不实现一个不同版本的GetTests方法,事实上,我想覆盖它,因为它内置在我的所有路由中:

class Concrete extends Test
{
    public function GetTests( $newArgument )
    {
        // notice $newArgument
    }
}

然而,我得到了这个错误消息:

Declaration of Concrete::GetTests() should be compatible with Test::GetTests()

除了从抽象类中复制整个函数之外,即使我只需要以不同的方式实现这一方法。。。有办法绕过这个吗?

我确实知道我可以:

abstract class Test
{
    abstract public function GetTests();
}

但这就是为什么我会打盹,因为我不再有能力修改底层Test类的实现方式。。。doh!。。。除非我真的不得不…


感谢所有优秀的答案

我决定自己取消斯诺克(这会很痛苦,但这是值得的),我将在Concrete类中实例化Test类,实现所有Test类方法的具体版本,然后在它们内部只调用实例化的Test类。。。这意味着在未来(或者现在)我不能简单地称之为。。。

上下文:

/* no longer abstract */ class UnitOfWorkController
{
    public function GetUnits()
    {
        // Implementation
        return View::make(...);
    }
}

而且。。。

class SomethingController /* no longer extends the UnitOfWorkController */
{
    private $unitOfWorkController;
    public function __Construct()
    {
        $this->unitOfWorkController = new UnitOfWorkController();
    }
    public function GetUnits()
    {
        return $this->unitOfWorkController->GetUnits();
        // or I could just implement my own junk
    }
}

您的具体子类违反了Liskov替换原则,长话短说,该原则说,如果X类的对象可以由给定的代码段处理,那么X的每个可能的子类也必须能够由同一代码段处理。

假设我想制作Test的另一个子类,并想实现我自己的GetTests方法。基类方法根本不接受任何参数,因此这表明,如果我的子类要替换它的超类,那么我对该方法的实现也不能接受任何参数。如果我给出实现参数,那么它就不再符合超类所规定的规范。

如果我有这样的代码:

$object = new Test;
$test -> GetTests ();

那么我不能在不更改要传入参数的调用代码的情况下替换Test的子类。同样,如果我确实更改了它,那么我有另一个不需要GetTests参数的Test子类,那么代码将不得不再次更改。事实上,如果不跳过一些环节来确定实际的类并使用适当的调用约定,就无法将相同的代码与两个子类一起使用,这意味着需要了解我将要使用的类的一些不需要知道的信息。

PHP在子类方法签名与其超类匹配方面不如大多数OO语言严格,但如果不匹配,它会发出警告。修复警告的唯一方法是让所有子类都具有与其继承的超类相同的方法签名。

子方法必须与父类中的相同方法具有相同的签名。这包括所需的参数及其类型。

例如,以下方法的子类也必须有一个参数,并且该参数必须强制转换为ArgumentType类或其子类。

public function something(ArgumentType $Argument)
{
}

但是,您可以通过将参数设置为null或任何其他值使其可选:

public function something(ArgumentType $Argument = null)
{
}

在这种情况下,子方法可能会省略此参数。

从PHP文档中,请参阅http://php.net/manual/en/language.oop5.abstract.php:

[…]此外,方法的签名必须匹配,即类型提示和所需参数的数量必须相同。例如,如果子类定义了一个可选参数,而抽象方法的签名没有,则签名中没有冲突。

Concrete::GetTests()的方法签名有一个变量,而Test::GetTests()没有。由于您已经在Test中定义了此方法,现在正在继承它。继承的版本与重写的版本不兼容。

以下是您的选择:

  1. $newArgument添加到Test::GetTests()中的参数列表中
  2. Concrete::GetTests()中的参数列表中删除$newArgument
  3. Concrete::GetTests()重命名为其他名称

PHP不支持这一点,正如错误消息所说。如果你想覆盖这个函数,它必须有相同的封装,在你的情况下,它不是

你可以使用一种神奇的方法:http://php.net/manual/en/language.oop5.overloading.php#object.call

参数数组是一个单独的实体,因此您可以在代码中"决定"(可以覆盖)如何处理哪个参数。

我想链接我读到的关于这件事的博客,但找不到我想的那个。有一个格式相当奇怪的,不确定它是否有什么好处,但它确实涉及到了一些问题。

很明显,您可以将参数添加到父项中,但这是向上"泄漏"的。如果其他孩子想要更多,你会得到一大群随机参数,所有参数都可以为空。