Php 我对Liskov替代原理有什么误解
我以为我理解LSP,但似乎我完全错了。我有以下课程:Php 我对Liskov替代原理有什么误解,php,liskov-substitution-principle,Php,Liskov Substitution Principle,我以为我理解LSP,但似乎我完全错了。我有以下课程: class PrimitiveValue { } class StringValue extends PrimitiveValue { } class A { public function foo(StringValue $value) { } } class B extends A { public function foo(PrimitiveValue $value) { } } 请注意,基类A
class PrimitiveValue {
}
class StringValue extends PrimitiveValue {
}
class A {
public function foo(StringValue $value) {
}
}
class B extends A {
public function foo(PrimitiveValue $value) {
}
}
请注意,基类A接受子类StringValue作为参数类型,子类B接受父类PrimitiveValue。我这么说是因为一个类似的问题,仅仅是反向类型,到处都会被问到 根据我的理解,类B中的方法foo()接受父方法接受的任何内容,以及更多内容,因为它接受基类型原语值。因此,任何只看到类A的调用方都会传递可由B处理的值,而不会违反LSP。知道是a B的调用方可以做出更有力的假设,并且可以自由地传递其他基本值子类型 然而,当我执行代码时,我得到了错误: Strict(2048):B::foo()的声明应该与A::foo(StringValue$value)兼容[APP/Controller/TestLocalController.php,第17行]
我做错了什么?我认为最有帮助的例子是如何传递违反代码对该值所做假设的值。假设我想要实现的目标是明确的,请添加关于如何正确实现的代码。PHP OOP不是这样工作的。您可以使用接口来完成此操作,例如:
<?php
interface CommonInterface {
}
class PrimitiveValue implements CommonInterface {
}
class StringValue extends PrimitiveValue implements CommonInterface {
}
class A {
public function foo(CommonInterface $value) {
}
}
class B extends A {
public function foo(CommonInterface $value) {
}
}
是的,考虑到您所展示的部分,不存在因不兼容类型而产生错误的情况。然而,谁说这将是最终的班级结构?将来您可以将StringValue
与PrimitiveValue
分离。方法签名必须“自身”兼容,以避免在更改看似不相关的代码时出现此类未来故障。只要看看签名本身,它们显然是不相容的。只有具备另外两类的额外知识,签名才能被认为是兼容的。这是太多的交叉耦合;这就是“代理兼容性”。类型之间的耦合比这更松散。类型提示中的类型名称不能推断实现。您的“兼容性”只源于您的类型的当前实现,而不是源于类型签名本身
您的签名不同,因此不兼容。您不能保证这些签名当前或将来的兼容性。没有正式规定StringValue
和PrimitiveValue
之间有任何兼容关系。您可能有一个这样的当前实现,但类型签名不能知道或依赖于此
作为一个实际例子,这是可行的:
require 'my_types.php'; // imports StringValue
(new B)->foo(new StringValue);
这并不是:
require 'my_alternative_type_implementations.php'; // imports StringValue
(new B)->foo(new StringValue); // fatal error: expected PrimitiveValue
B
中的类型签名没有任何更改,但在执行过程中仍然会中断
我做错了什么?
我认为最有帮助的例子是如何传递违反代码对该值所做假设的值
您没有误解LSP,在子类方法中允许更大的类型并不违反原则;事实上,您所描述的是众所周知的,PHP中不支持的设计或实现困难
就个人而言,我对这种方法唯一的保留是,子类中较弱类型的处理被超类隐藏
假设我想要实现的目标是明确的,请添加关于如何正确实现的代码
PHP中的方法参数类型是不变的,即在重写方法时,每个参数的类型(如果在父参数中提供)必须完全匹配。虽然在中讨论了此行为,并在的介绍中再次提出,但即使在下一个主要版本中也不能保证提供此行为
为了克服这一点,您目前被迫使基本类型和派生类型实现相同的接口,然后在父类和子类中使用该接口,如所述。问题在于严格的标准不允许您更改扩展类中覆盖的方法的签名,因此foo()B
中的必须与A
中的foo()具有相同的签名。这是一个基本的PHP限制。将B
中foo()
的签名更改为public function foo(StringValue$value){
一般来说,您没有错,不过我认为重载继承的方法并不一定是好的做法,这样它就可以接受其父级不接受的类型。parent::foo($value)呢
?那么你应该换一种方法。从LSP的角度来看,这并不是不正确的;PHP是否试图在这里寻找你并实施最佳实践,或者PHP是否只是没有实现它应该/可能实现的LSP检查,这是有争议的。我投票将这个问题作为离题回答,因为它更适合to programmers.stackexchange.comIt不违反LSP,但PHP中的方法重写是类型不变的(iirc)这就是为什么这是不允许的…也就是说,你似乎不应该这样做;-)实际上这是一个关于代码错误的技术问题,所以它适合于so,而不是programmers.SE。虽然关于程序员的LSP有很多好问题,但这是关于一个似乎有错误的特定PHP实现的问题我想说,接口很好,但这实际上并不能回答问题。OP问他为什么不能让子类方法以超类作为参数。“只要看看签名本身,它们显然是不兼容的。”--为什么?“只有具备另外两个类的额外知识,签名才能被认为是兼容的”--调用对象的方法时也可以这样说,对象的类不声明,只继承。为什么后者是合法的?它们显然是不兼容的,因为它们是不同的类型。B