What is wrong with this example of class inheritance? [duplicate]

家住魔仙堡 提交于 2020-01-30 09:14:08

问题


This code of dependency inversion should work fine but instead it gives error.

What am I doing wrong in here?

interface A  { }

abstract class B implements A { }

class C extends B { }

abstract class D {
    public function foo(A $a) { }
}

class E extends D {
    public function foo(C $c) { }
}

The error is:

Warning: Declaration of E::foo(C $c) should be compatible with D::foo(A $a) in [...][...] on line 24

Surprisingly, doing the same for the constructor method works just fine:

interface A  { }

abstract class B implements A { }

class C extends B { }

abstract class D {
    public function __construct(A $a) { }
    public function foo(A $a) { }
}

class E extends D {
    public function __construct(C $c) { }
    public function foo(A $c) { }
}

回答1:


There are two questions here.

Why the first part raises a WARNING: the error is pretty explicit. Any time you override a method you need to respect the parent's signature, so a client application's expectations are met.

class Foo {}

class Bar extends Foo {}

class A {
   public function test(Foo $foo) {}
}

class B extends A {
   public function test(Bar $bar) {}
}

Now, since B extends A, and A::test() expects a Foo, a user might try to do this:

$a = new A();
$b = new B();

$foo = new Foo();
$bar = new Bar();

$b->test($bar);
$b->test($foo);

The first call to test() would work, but the second would fail (and fatally): a Foo is not a Bar.

The parameter type is more restrictive than the original, and thus breaks expectations from client applications. Note that if we had a legal declaration (B::test(Foo)), we could still call $b->test($bar) and would work as expected, so changing the signature is not really necessary.

In November 2019, When PHP 7.4 is released, contravariance will be supported for parameter types, and covariance will be supported for return types; so your example would still be invalid.

Finally, notice that the error raised is a WARNING, it won't crash the program immediately. But it is warning you that you are opening your application to behaviour that might crash it, and that's it's generally poor design (for the reasons explained above).


The second question: Why you do not get the same warning for the constructor? They do not raise this warning by design. If you check the docs, you'll find this:

Unlike with other methods, PHP will not generate an E_STRICT level error message when __construct() is overridden with different parameters than the parent __construct() method has.

Why constructors are treated differently? Because it is generally understood that constructors are not part of the "public API" of an object, and do not need to be subjected to the same constraints. The Liskov Substitution Principle deals with objects, not with classes; and before an class is instantiated there is yet no object. Thus, the constructor is out of scope.

You can read more about this here, for example.




回答2:


TL;DR C is implementing A, but it can also inherit other methods from B which implementing A doesn't guarantee. This creates situation where two classes of same inherited type can have two methods with different signatures. A sub-class must accept all inputs that the parent class would accept, but it can accept additional inputs if it wants,

Let's extend your example.

Nothing crazy, just some example methods.

interface A
{
    public function gotA();
}

abstract class B implements A
{
    abstract public function gotB();
}

class C extends B
{
    public function gotA()
    {
        // ...
    }

    public function gotB()
    {
        // ...
    }
}

I can agree that C is indirectly implementing A, but it also contains methods from B, so the call in E::foo() would be perfectly fine.

abstract class D
{
    public function foo(A $a)
    {
        $a->gotA();
    }
}

class E extends D
{
    public function foo(C $c)
    {
        $c->gotB();
    }
}

Another example class implementing A.

class ExampleA implements A
{
    public function gotA()
    {
        // ...
    }
}

Now the fun part. Assume this function anywhere else in your code. It assumes D (expecting A in foo) as parameter.

But E is also instanceof D and can be passed to x, but it expects C in foo which ExampleA has nothing to do with.

function x(D $d)
{
    $exampleA = new ExampleA();
    $d->foo($exampleA);
}


$e = new E();
x($e);

To sum up, the contract for function x has been met and yet the function broke.

You ended up with two 'different' D types. The one expecting A and the one expecting C.

As for why it doesn't throw warning with __constructor I don't know. Need further investigation.



来源:https://stackoverflow.com/questions/58107389/what-is-wrong-with-this-example-of-class-inheritance

标签
易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!