我正在详细了解LSP,我确实理解为什么强化前提条件违反了原则(使用http://www.ckode.dk/programming/solid-principles-part-3-liskovs-substitution-principle/#contravariance中的示例):
//fill vectors
for (unsigned int i = 0; i < t.size() - 1; i++)
if (abs(t[i] - t[i + 1]) < dT)
t.erase(t.begin() + (i + 1));
for (unsigned int j = 0; j < p.size() - 1; j++)
if (abs(p[j] - p[j + 1]) < dP)
p.erase(p.begin() + (j + 1));
在这里,我清楚地看到,对于它的派生而言,对基类有效的东西会失败。换句话说,我无法在不改变行为的情况下用其派生类替换基类。
现在,以下方法被认为是合法的,因为它削弱了前提条件:
erase
引用网站:这是完全合法的,因为超类型的任何有效参数也将在子类型中有效。
嗯,但无效参数怎么样?如果我有一个使用无效参数调用SuperType的代码(例如空名称),则会失败。如果我用子类型替换它,相同的调用将不会失败,因为条件较弱。所以在这个意义上,我不能用子类型替换超类型,因为它也会改变行为。我很困惑。
答案 0 :(得分:1)
如果削弱前置条件,子类型仍然与期望超类型的位置兼容。它可能不会抛出基类正常的异常,但这没关系,因为抛出较少的异常不应该破坏消费代码。如果调用代码是基于在某些地方抛出异常并将其用于应用程序的主要控制流的假设而构建的,则应该重写消费代码。
另外,我认为你的第二个代码示例是错误的。
如果必须始终强制执行基类的前提条件,则更好的实现方法是创建封装这些规则的数据类型,并将其作为参数传递。这样它就不在子类的手中,它是新类构造函数的一部分。
例如:
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
}
}
答案 1 :(得分:0)
具有前置条件和后置条件的方法表明,当调用者满足前提条件时,则保证在退出时满足后置条件。但是,如果前提条件不满足,则合同不会说明会发生什么 - 仍然允许该方法成功完成。因此,子类型可以削弱前置条件,因为如果方法无法满足子类型的前提条件,则调用者无法对方法的行为做出任何假设。