Liskov替换原理的基本要点是,可以用遵循相同契约(行为)的子类替换超类。或正如马丁·福勒(Martin Fowler)所说:“使用指针或对基类的引用的函数必须能够在不了解的情况下使用派生类的对象。”
提到参数自变量是LSP的一部分,但是我似乎无法理解它为什么以及如何工作。也就是说,在子类中,重写方法可以接受更宽的(较少派生的参数)。
像这样的东西:
class Base
{
int GetLenght(string s)
{
return s.lenght;
}
}
class Derived: Base
{
override int GetLenght(object s)
{
?? I cannot return any lenght of an object..
}
}
这怎么可能工作?我的意思是,如果较少派生的参数不具有我需要的属性,我该如何遵守合同?
PS:我确实知道大多数OO语言都不支持该功能,我只是很好奇。
最佳答案
让我们稍微更改一下示例:
让我们先假设这是一个实现Sequence
方法的接口GetLength
,并且String
实现该接口。
我们还假设在您的示例中,使用的对象类型不是Sequence
,而是String
(在这种情况下,它是实际的实现)。
Base base;
Derived derived;
String s;
Sequence o;
int i;
i = base.GetLength(s); // valid
i = derived.GetLength(o); // valid
i = base.GetLength(o); // obviously invalid
base = derived;
base.GetLength(s); // valid
base.getLength(o); // still invalid,
// the contract of "Base" still requires an argument of type string,
// despite actually being of type "Derived"
您的实际实现是无关紧要的,重要的是类型。只要在获取字符串时不破坏功能,就可以将浮标中的任何内容作为任意对象的长度返回,例如:
class Derived : Base {
override int GetLenght(Sequence s) {
return s.GetLength();
}
}
如您所见,您可以给
derived
任何类型的Sequence
,但是Base
仍然需要特定类型的String
。因此,在许多情况下,协变的工作原理不会违反LSP。正如您在自己的示例中看到的那样,在这方面可以说没有违反LSP的情况下,您不能使用
object
而不是string
(您可以并且Base / Derived仍然不违反LSP,LSP违规隐藏在内部派生的,对外部不可见)。埃里克·利珀特(Eric Lippert)有一些非常不错的文章,涉及C#中的协方差和协方差,从
Covariance and Contravariance in C#, Part One(至第11部分)。
可以在这里找到更多信息:
Covariance and Contravariance
附带说明:
尽管不违反LSP是值得争夺的东西,但它并不总是最经济的选择。与第三方或旧式API一起使用时,有时仅破坏LSP可以节省理智,时间和资源。