Что такое принцип подстановки Барбары Лисков?

«Что такое принцип подстановки Барбары Лисков?» — вопрос из категории ООП, который задают на 39% собеседований C# Разработчик. Ниже — развёрнутый ответ с разбором ключевых моментов.

Ответ

Принцип подстановки Лисков (Liskov Substitution Principle, LSP) — один из пяти принципов SOLID. Он гласит, что объекты производного класса должны быть заменяемы объектами своего базового класса без нарушения корректности программы. Проще говоря, если у вас есть функция, работающая с базовым классом, она должна продолжать корректно работать, если вы передадите ей любой из его наследников.

Суть принципа: Наследование должно означать отношение «является» (is-a) в поведенческом, а не только в структурном смысле. Наследник не должен ужесточать предусловия или ослаблять постусловия методов базового класса.

Классический пример нарушения LSP (Прямоугольник и Квадрат):

// Базовый класс
public class Rectangle
{
    public virtual int Width { get; set; }
    public virtual int Height { get; set; }

    public int Area => Width * Height;
}

// Наследник, нарушающий LSP
public class Square : Rectangle
{
    public override int Width
    {
        set { base.Width = value; base.Height = value; }
    }
    public override int Height
    {
        set { base.Width = value; base.Height = value; }
    }
}

// Клиентский код, который сломается
public void TestRectangleArea(Rectangle rect)
{
    rect.Width = 5;
    rect.Height = 4;
    Console.WriteLine($"Expected area: 20, Actual area: {rect.Area}"); // Для Square будет 16!
}

Почему это нарушение? Клиентский код TestRectangleArea ожидает, что ширина и высота прямоугольника изменяются независимо. Квадрат же нарушает это поведенческое ожидание, делая их зависимыми.

Как исправить (следовать LSP):

  1. Избегать неподходящего наследования. Квадрат и прямоугольник могут быть разными сущностями.
  2. Использовать общий интерфейс или абстрактный класс, если они действительно имеют общее поведение.
public interface IShape
{
    int CalculateArea();
}

public class Rectangle : IShape { /* ... */ }
public class Square : IShape { /* ... */ }

Вывод: Следование LSP делает систему более устойчивой к изменениям, позволяет безопасно использовать полиморфизм и избегать скрытых ошибок.