当前位置: 首页 > 知识库问答 >
问题:

为什么弱化前提条件不违反利斯科夫替代原则

楚俊逸
2023-03-14

我正在详细学习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;  
    }  
}  

引用站点:这是完全合法的,因为超类型的任何有效参数在子类型中也是有效的。

好吧,但是无效参数呢?如果我有一个用无效参数(例如空名称)调用超类型的代码,它将失败。如果我用子类型替换它,相同的调用不会失败,因为条件较弱。因此,在这个意义上,我不能用子类型替换超类型,因为它也会改变行为。我很困惑。

共有2个答案

梁丘佑运
2023-03-14

带有前置和后置条件的方法是声明当调用方满足前置条件时,它保证后置条件在退出时得到满足。然而,合同没有说明如果先决条件不满足会发生什么——仍然允许该方法成功完成。因此,子类型可以削弱先决条件,因为如果调用者不能满足子类型的先决条件,他们就不能对方法的行为做出任何假设。

鲜于喜
2023-03-14

如果弱化了一个前提条件,则子类型仍然与需要超类型的位置兼容。它可能不会在基类正常情况下抛出异常,但这没关系,因为抛出较少的异常不会破坏使用代码。如果调用代码是基于在某些地方抛出异常的假设构建的,并将其用于应用程序的主控制流,则可能应该重写使用代码。

另外,我认为您的第二个代码示例是错误的。

如果基类的前提条件真的必须一直强制执行,更好的实现是创建封装这些规则的数据类型,并将其作为参数传递。这样它就不在子类的手中,它是新类构造函数的一部分。

前任:

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(或者至少会导致违反),因为基类的行为可能会在某个点上发生更改,并且您的子类可能会丢失该行为,从而导致子类不再可以替换父类。这对我来说似乎是有道理的,但如果有人能详细说明这一