Warning: file_get_contents(/data/phpspider/zhask/data//catemap/2/batch-file/5.json): failed to open stream: No such file or directory in /data/phpspider/zhask/libs/function.php on line 167

Warning: Invalid argument supplied for foreach() in /data/phpspider/zhask/libs/tag.function.php on line 1116

Notice: Undefined index: in /data/phpspider/zhask/libs/function.php on line 180

Warning: array_chunk() expects parameter 1 to be array, null given in /data/phpspider/zhask/libs/function.php on line 181
Php 我对Liskov替代原理有什么误解_Php_Liskov Substitution Principle - Fatal编程技术网

Php 我对Liskov替代原理有什么误解

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

我以为我理解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()接受父方法接受的任何内容,以及更多内容,因为它接受基类型原语值。因此,任何只看到类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