我对利斯科夫替代原则有什么误解


What am I misunderstanding about the 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接受子类StringValue作为参数类型,子类B接受父类PrimitiveValue。我这么说是因为类似的问题,只是类型相反,到处都有人问。

根据我的理解,类B中的方法foo()接受父方法接受的任何东西,加上更多,因为它接受基类型PrimitiveValue。因此,任何只看到类A的调用者都会传递可以由B处理的值,而不会违反LSP。知道它是B的调用者可以做出更有力的假设,并可以自由传递其他PrimitiveValue子类型。

然而,当我执行代码时,我会得到错误:

Strict(2048):B::foo()的声明应该与A::foo兼容(StringValue$value)[AP/Controller/TestLocalController.php,第17行]

我做错了什么?我认为最有用的是一个例子,说明如何传递违反代码对该值的假设的值。假设我想实现的目标很清楚,请添加关于如何正确实现的代码。

我做错了什么?我认为最有用的是一个例子,说明如何传递违反代码对该值的假设的值。

您没有误解LSP,在子类方法中允许更大的类型并不违反原则;事实上,您所描述的是反变量方法参数类型,这在PHP中是不受设计或实现困难支持的。

就我个人而言,我对这种方法唯一的保留是,超类隐藏了对子类中较弱类型的处理。

假设我想实现的目标很清楚,请添加关于如何正确实现的代码。

PHP中的方法参数类型是不变的,即重写方法时,每个参数的类型(如果在父级中提供)必须完全匹配。虽然这种行为在过去已经被讨论过,最近随着返回类型提示的引入再次被提出,但即使在下一个主要版本中也不能保证它可用。

为了克服这一点,您目前被迫让基元类型和派生类型实现相同的接口,然后在父类和子类中都使用该接口,正如这个答案所描述的那样。

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) {
    }
}

是的,考虑到您所展示的部分,不存在因不兼容类型而产生错误的情况。然而,谁说这将是最终的阶级结构呢?你将来可能会把StringValuePrimitiveValue离婚。方法签名必须"本身"兼容,以避免在更改看似不相关的代码时将来出现这种故障。仅从签名本身来看,它们显然是不兼容的只有对另外两个类有了额外的了解,签名才能被认为是兼容的。这是太多的交叉耦合;这就是"代理兼容性"。类型之间的耦合更为松散。类型提示中的类型名称不会推断实现。您的"兼容性"仅源于类型的当前实现,而不是源于类型签名本身。

您的签名不同,因此不兼容。您无法保证这些签名的当前或未来兼容性。没有任何地方正式规定StringValuePrimitiveValue具有任何兼容关系。您可能有当前实现,但类型签名无法知道或依赖于此。

作为一个实际的例子,这是有效的:

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中的类型签名没有任何变化,但在执行过程中仍然会中断。