PHP接口实现拒绝子类参数。

13

考虑以下内容:

class A{}

class B extends A{}

interface I{
 // expects object instanceof A
 function doSomething(A $a);
}

class C implements I
{
 // fails ????
 function doSomething(B $b){}
}

在我看来,上述代码应该可以工作,但实际运行时,php拒绝了这个实现方式,要求第一个参数的类型(A)必须与接口(I)中定义的一模一样。由于B是A的子类,我不明白问题出在哪里。我有什么遗漏吗?


3
B是从A继承而来的,但不等于A,PHP对此有严格的观点。这就是它的方式,我认为无法规避它——你可能只需要在没有类型提示的情况下工作。 - Pekka
5
Liskov替换原则:如果B扩展了A,那么没有理由C::doSomething()只接受B,而不是所有A类型的对象。 - Mchl
2
@Wiseguy:我的观点是PHP在拒绝这个定义上是完全正确的。 - Mchl
通过限制范围,您正在违反接口I的契约。如果接口指定可以使用A调用doSomething,而您的实现不能使用A调用(任何A,不仅仅是A的子集),那么您的代码肯定没有实现所需的行为。 - vbence
显示剩余4条评论
2个回答

11

class C 实现 I 意味着 CI 必须存在子类型关系。也就是说,C 类型的对象应该可以在需要 I 类型对象的任何地方使用。

在您的情况下,CI 更为严格,因为它对其 doSomething 参数有更精确的要求——I.doSomething 可以接受任何 A,但 C.doSomething 要求一个特定子类型的 A

请注意,如果您将 C.doSomething 更改为接受任何 A,则没有任何东西会阻止您将其传递给 B 类型的对象。但您不能仅要求 B,因为那样会打破子类型契约。

理论上,子类型可以在函数参数方面更加自由,在返回类型方面更加具体(但永远不可能反过来,就像在您的情况中一样)。实际上,编程语言可能要求重写方法中的参数类型必须相同。


1
你的回答的第二段解释了我需要知道的一切。谢谢。 - fabio
如果 B 实现了所有的 A 功能并且还有额外的功能,那么 C 如何比 I 更加约束? - Cypher
1
@Cypher -- 如果 B 实现了所有 A 的功能并且还有更多的功能,那么它比 A 更强大,因此 CdoSomething 需要比 IdoSomething 更强大的参数(这只是一个“纯粹”的 A)。换句话说,CdoSomethingIdoSomething 更挑剔。 - Aivar

4
理论上,子类型可以在其函数参数方面更加自由,在其返回类型方面更加具体(但从未反之而行,就像您的情况一样)。实际上,编程语言可能要求重写方法中的参数类型必须相同。为了解决这个问题,可以使用一个小技巧-使用instanceof
class A{}

class B extends A{}

interface I{
 // expects object instanceof A
 function doSomething(A $a);
}

class C implements I
{
 
 function doSomething(A $b){
   if($b instanceof of B){
   //do something
   }else{throw new InvalidArgumentException("arg must be instance of B") };
 }
}

1
“is instance of” 应该不是 “instanceof” 吗? - commonpike

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接