Ответ
Принцип подстановки Лисков (Liskov Substitution Principle, LSP): Объекты производного класса должны быть заменяемы на объекты базового класса без нарушения корректности программы. Клиентский код, работающий с базовым типом, должен продолжать работать корректно с любым из его подтипов.
Классический пример нарушения LSP — «Прямоугольник-Квадрат»:
// Базовый класс
public class Rectangle
{
public virtual int Width { get; set; }
public virtual int Height { get; set; }
public int Area => Width * Height;
}
// Производный класс, нарушающий контракт базового
public class Square : Rectangle
{
public override int Width
{
get => base.Width;
set { base.Width = value; base.Height = value; } // Нарушение!
}
public override int Height
{
get => base.Height;
set { base.Height = value; base.Width = value; } // Нарушение!
}
}
// Клиентский код, который сломается
public void TestRectangleArea(Rectangle rect)
{
rect.Width = 5;
rect.Height = 4;
Console.WriteLine("Expected area: 20, Actual area: " + rect.Area);
}
// Вызов
var sq = new Square();
TestRectangleArea(sq); // Выведет: Expected area: 20, Actual area: 16
// Квадрат не может быть заменой прямоугольника в этом контексте!
Как исправить (соблюсти LSP): Нужно пересмотреть иерархию наследования. Квадрат — это частный случай прямоугольника с математической точки зрения, но не с точки зрения поведения объекта, изменяющего свои стороны независимо.
// Решение 1: Не связывать наследованием, использовать композицию или общий интерфейс.
public interface IShape
{
int GetArea();
}
public class Rectangle : IShape { /* ... */ }
public class Square : IShape { /* ... */ }
// Решение 2: Сделать классы неизменяемыми (immutable).
public class Rectangle
{
public Rectangle(int width, int height)
{
Width = width;
Height = height;
}
public int Width { get; } // Только getter
public int Height { get; }
public int Area => Width * Height;
}
public class Square : Rectangle
{
// Квадрат создается с одним параметром, что логично.
public Square(int side) : base(side, side) { }
}
// Теперь подстановка безопасна, так как состояние объектов нельзя изменить после создания.
Вывод: LSP заставляет проектировать иерархии так, чтобы подтипы не ужесточали предусловия и не ослабляли постусловия базового типа.