我以为我理解LSP,但似乎我完全错了。我有以下课程:
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()接受父方法接受的任何内容,加上更多,因为它接受基类型PrimitiveValue。因此,任何看到A类的调用者都会传递可以由B处理的值,而不是违反LSP。知道它是B的来电者可以做出更强的假设并且可以自由地传递其他PrimitiveValue子类型。
然而,当我执行代码时,我收到错误:
严格(2048):B :: foo()的声明应与A :: foo(StringValue $ value)兼容[APP / Controller / TestLocalController.php,第17行]
我错了什么?我认为最有用的是一个如何传递一个值的例子,这个值违反了代码对该值的假设。假设我很清楚我想要实现的目标,请同时添加如何正确执行的代码。
答案 0 :(得分:1)
这不是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) {
}
}
答案 1 :(得分:1)
我错了什么? 我认为最有用的是一个如何传递一个值的例子,这个值违反了代码对该值的假设。
你没有误解LSP,允许子类方法中更大的类型不违反原则;实际上,您所描述的内容被称为contravariant method argument types,这在PHP中不受设计或实现困难的支持。
就个人而言,我对此方法的唯一保留是超类隐藏了子类中较弱类型的处理。
假设我很清楚我想要实现的目标,请同时添加有关如何正确执行此操作的代码。
PHP中的方法参数类型是不变的,即每个参数的类型(如果在父级中提供)必须与覆盖方法完全匹配。虽然past已经讨论了这种行为,但recently引入了return type hinting,但即使在下一个主要版本中也无法保证可用。< / p>
为了解决这个问题,您目前不得不使原始类型和派生类型实现相同的接口,然后在父类和子类中使用它,如this answer所述。
答案 2 :(得分:0)
是的,鉴于您展示的作品,您不会产生因不兼容类型而产生错误的情况。但是,谁说这将是最终的阶级结构?您可以在将来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
中的类型签名没有任何变化,但在执行期间仍然破坏了。