Warning: file_get_contents(/data/phpspider/zhask/data//catemap/0/drupal/3.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
Oop 为什么削弱一个先决条件不违反利斯科夫替代原则_Oop_Solid Principles_Liskov Substitution Principle - Fatal编程技术网

Oop 为什么削弱一个先决条件不违反利斯科夫替代原则

Oop 为什么削弱一个先决条件不违反利斯科夫替代原则,oop,solid-principles,liskov-substitution-principle,Oop,Solid Principles,Liskov Substitution Principle,我正在详细学习LSP,我确实理解为什么强化先决条件违反了原则(使用以下示例): 引用站点:这是完全合法的,因为超类型的任何有效参数在子类型中也是有效的 好吧,但是无效参数呢?如果我有一个用无效参数(例如空名称)调用超类型的代码,它将失败。如果我用子类型替换它,相同的调用不会失败,因为条件较弱。所以从这个意义上说,我不能用子类型替换超类型,因为它也会改变行为。我很困惑。如果你弱化了一个前提条件,子类型仍然与期望超类型的地方兼容。它可能不会在基类正常情况下抛出异常,但这没关系,因为抛出较少的异常不会

我正在详细学习LSP,我确实理解为什么强化先决条件违反了原则(使用以下示例):

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


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

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

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

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

例:

公共类用户名
{
公共字符串值{get;}
公共用户名(字符串值)
{
if(string.IsNullOrWhitespace(value)| | value.Length<4)
抛出新ArgumentNullException(nameof(value));
价值=价值;
}
}
公共类基类
{
公共虚拟void Foo(用户名)
{ 
//这里不需要先决条件检查
}
}
公共类派生类:基类
{
公共覆盖无效Foo(用户名)
{
//这里不需要先决条件检查
}
}

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

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

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

例:

公共类用户名
{
公共字符串值{get;}
公共用户名(字符串值)
{
if(string.IsNullOrWhitespace(value)| | value.Length<4)
抛出新ArgumentNullException(nameof(value));
价值=价值;
}
}
公共类基类
{
公共虚拟void Foo(用户名)
{ 
//这里不需要先决条件检查
}
}
公共类派生类:基类
{
公共覆盖无效Foo(用户名)
{
//这里不需要先决条件检查
}
}

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

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

您的第二个示例增强了前提条件。啊,对不起,错误的代码段!你是对的,你的第二个例子加强了前提条件。啊,对不起,错误的代码片段!你是对的,你是对的,我从示例中放置了正确的代码片段。但是我仍然不明白——如果我用它的派生类替换基类,并且行为发生了变化(如示例中所示),那怎么仍然可以呢?如示例中所示-基类不允许空字符串,但派生类允许空字符串,这会将空字符串削弱为不为null的字符串。这是.NET和类似系统中一般异常抛出模式的一种问题。抛出的异常不是类型的公共接口的一部分,因此在编译时很难强制执行这些契约。从
超类型
类之外,没有明确的方法知道它有什么先决条件。通过创建
用户名
类型,可以使类型系统强制执行这些合同。LSP并不真正考虑从弱化的前提条件下突破的可能性,但它是可能的。也许LSP的另一面是不设计类,这样子类就可以破坏它们。我认为它与异常没有特别的关系。假设基类在a,B,C为真时执行一个函数,而它的导数在只有a为真时执行相同的函数。非常正确。这不是特例。SOLID的很大一部分通常是,在使用类时,不要对不属于其公共接口的任何内容进行任何假设。这包括抛出异常
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
    }
}