该原理描述了 3 条规则:
不能在子类中强制执行前提条件。换句话说,子类不应该创建比基类中定义的更多的前提条件来执行某些行为。
后置条件不能在子类中放宽。也就是说,子类必须满足基类中定义的所有后置条件。
不变量——基类的所有条件——也必须存储在子类中
似乎一切都是合乎逻辑的,但我不明白在这种情况下,可以用继承做什么,以免违反这个原则......
例如,这里是 metanit 的一个例子:强化前置条件:
class Account
{
public int Capital { get; protected set; }
public virtual void SetCapital(int money)
{
if (money < 0)
throw new Exception("Нельзя положить на счет меньше 0");
this.Capital = money;
}
}
class MicroAccount : Account
{
public override void SetCapital(int money)
{
if (money < 0)
throw new Exception("Нельзя положить на счет меньше 0");
if (money > 100)
throw new Exception("Нельзя положить на счет больше 100");
this.Capital = money;
}
}
放宽后置条件:
class Account
{
public virtual decimal GetInterest(decimal sum, int month, int rate)
{
// предусловие
if (sum < 0 || month >12 || month <1 || rate <0)
throw new Exception("Некорректные данные");
decimal result = sum;
for (int i = 0; i < month; i++)
result += result * rate / 100;
// постусловие
if (sum >= 1000)
result += 100; // добавляем бонус
return result;
}
}
class MicroAccount : Account
{
public override decimal GetInterest(decimal sum, int month, int rate)
{
if (sum < 0 || month > 12 || month < 1 || rate < 0)
throw new Exception("Некорректные данные");
decimal result = sum;
for (int i = 0; i < month; i++)
result += result * rate /100;
return result;
}
}
我有些不明白,但是如果我真的需要改变遗留行为中的一些行为怎么办?
或者有一个接口并且 10 个类实现了这个接口 => 他们可以实现不同的行为,因为没有初始实现。
这个原则已经讨论过很多次了(例如一、二、三)
一般来说,听起来像
如何做到这一点?
假设您有一个界面。并且这个接口有一个描述(形象地说,一个契约,也就是这个接口的成员应该做什么的描述),比如这里是IList ,我们来看看它的Add方法
此处明确说明了实现的行为。因此,无论您使用哪种列表实现,该方法都必须接受一个对象并返回一个索引。
你怎么能打破先决条件?很简单
该接口并没有明确声明您可以添加一些特殊元素,因此,如果您想要的所有对象都不能添加到这个非通用列表中,该接口的客户会非常惊讶。
如何打破后置条件?很简单
该接口明确指出,成功插入集合后,该方法必须返回正确的索引。如果我们打破这个规则,我们就打破了接口的契约。
所以里氏替换原则告诉你,你可以随心所欲地实现你的类,但你必须遵守接口或基类的约定。在这种情况下,如果已经编写了根据接口契约与 IList 一起操作的代码,那么您可以将 IList 的任何实现滑入其中,并且代码应该继续成功运行。