我正在详细学习LSP,我确实理解为什么强化先决条件违反了这一原则(使用来自http://www.ckode.dk/programming/solid-principles-part-3-liskovs-substitution-principle/#contravariance):
public class SuperType
{
public virtual string FormatName(string name)
{
if (string.IsNullOrEmpty(name))
throw new ArgumentException("name cannot be null or empty", "name");
return name;
}
}
//VIOLATING ONE
public class LSPIllegalSubType : SuperType
{
public override string FormatName(string name)
{
if (string.IsNullOrEmpty(name) || name.Length < 4)
throw new ArgumentException("name must be at least 4 characters long", "name");
return name;
}
}
在这里,我清楚地看到,对于基类有效的东西对于它的派生类将失败。换句话说,在不改变行为的情况下,我无法用基类的导数替换基类。
现在,以下方法被认为是合法的,因为它削弱了先决条件:
public class LSPLegalSubType : SuperType
{
public override string FormatName(string name)
{
if (name == null)
throw new ArgumentNullException("name");
return name;
}
}
引用站点:这是完全合法的,因为超类型的任何有效参数在子类型中也是有效的。
好吧,但是无效参数呢?如果我有一个用无效参数(例如空名称)调用超类型的代码,它将失败。如果我用子类型替换它,相同的调用不会失败,因为条件较弱。因此,在这个意义上,我不能用子类型替换超类型,因为它也会改变行为。我很困惑。
带有前置和后置条件的方法是声明当调用方满足前置条件时,它保证后置条件在退出时得到满足。然而,合同没有说明如果先决条件不满足会发生什么——仍然允许该方法成功完成。因此,子类型可以削弱先决条件,因为如果调用者不能满足子类型的先决条件,他们就不能对方法的行为做出任何假设。
如果弱化了一个前提条件,则子类型仍然与需要超类型的位置兼容。它可能不会在基类正常情况下抛出异常,但这没关系,因为抛出较少的异常不会破坏使用代码。如果调用代码是基于在某些地方抛出异常的假设构建的,并将其用于应用程序的主控制流,则可能应该重写使用代码。
另外,我认为您的第二个代码示例是错误的。
如果基类的前提条件真的必须一直强制执行,更好的实现是创建封装这些规则的数据类型,并将其作为参数传递。这样它就不在子类的手中,它是新类构造函数的一部分。
前任:
public class UserName
{
public string Value { get; }
public UserName(string value)
{
if (string.IsNullOrWhitespace(value) || value.Length < 4)
throw new ArgumentNullException(nameof(value));
Value = value;
}
}
public class BaseClass
{
public virtual void Foo(UserName username)
{
//No precondition checks required here
}
}
public class DerivedClass : BaseClass
{
public override void Foo(UserName username)
{
//No precondition checks required here
}
}
子类型的实际后置条件是通过组合(使用逻辑)基类型的后置条件和子类型的后置条件来创建的,这使得得到的后置条件更具限制性 以下是加强前置条件和削弱后置条件的例子,结果违反了LSP(链接): > 假设基类使用成员int。现在您的子类型要求int为正。这是强化的前提条件,现在任何以前用负整数工作得很好的代码都被破坏了。 示例: 基类postcondition保证方法的返回值在范围内,但随后子类型将post
我认为是这样的,因为子类在被替换时的行为不像基类,而且它抛出了一个RuntimeException? 我不完全确定,我想知道我的假设是否正确
现在,让我们来看看“燃料”类: 以上是完成的所有抽象类,现在让我们看看具体的实现。首先,fuel的两个具体实现,包括一些贫血接口,以便我们可以正确地键入-提示/嗅探它们: 最后,我们有了车辆的具体实现,它确保使用正确的燃料类型(接口)为特定的车辆类别加油,如果不兼容则抛出异常: null
有一个很好的例子,在圆-椭圆问题中违反了Liskov替换原理。 下面是一个流行的SO答案的措辞(尽管是矩形和正方形): 在数学中,是一个。实际上,它是矩形的特化。“is a”使您希望使用继承来对此进行建模。但是,如果在代码中使派生自,那么应该可以在任何需要的地方使用。这导致了一些奇怪的行为。 想象一下,您的矩形基类上有和方法;这似乎完全符合逻辑。但是,如果矩形引用指向,则和没有意义,因为设置一个将
以下代码是否直接违反了Liskov替换原则: 子类不应破坏父类的类型定义。 结果如下: 致命错误:b::baz(Foo $foo)的声明必须与a::baz(Baz $baz)兼容
我最近一直在深入研究一些坚实的设计原则,我从一个来源获得的一些信息最初对我来说是有意义的,但基于我能够找到的关于thr LSP的严格定义,这些信息似乎不正确。具体信息如下: 1) 不在重写的方法上回调super()会违反LSP(或者至少会导致违反),因为基类的行为可能会在某个点上发生更改,并且您的子类可能会丢失该行为,从而导致子类不再可以替换父类。这对我来说似乎是有道理的,但如果有人能详细说明这一