我对Liskov替代原则的误解是什么?

时间:2015-02-06 09:11:48

标签: 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接受子类 StringValue作为参数类型,子类B接受父类PrimitiveValue。我之所以这样说是因为类似的问题,只是反向类型,随处可见。

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

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

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

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

3 个答案:

答案 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)

是的,鉴于您展示的作品,您不会产生因不兼容类型而产生错误的情况。但是,谁说这将是最终的阶级结构?您可以在将来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中的类型签名没有任何变化,但在执行期间仍然破坏了。