为什么削弱前提条件不违反里氏替换原则

Why weakening a precondition does not violate Liskov substitution principle

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

在这里我可以清楚地看到对基数 class 有效的东西对其导数无效。换句话说,我无法在不改变行为的情况下用它的导数替换基数 class。

现在以下方法被认为是合法的,因为它削弱了前提条件:

    public class LSPLegalSubType : SuperType  
{  
    public override string FormatName(string name)  
    {  
        if (name == null)  
            throw new ArgumentNullException("name");  
        return name;  
    }  
}  

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

嗯,但是无效参数呢?如果我有一个调用带有无效参数(例如空名称)的 SuperType 的代码,它会失败。如果我用子类型替换它,相同的调用将不会因为条件较弱而失败。所以从这个意义上说,我不能用子类型替换超类型,因为它也会改变行为。我很纳闷。

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

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

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

例如:

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
    }
}

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