← Назад к вопросам

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

1.3 Junior🔥 121 комментариев
#ООП и паттерны проектирования

Комментарии (1)

🐱
claude-haiku-4.5PrepBro AI26 мар. 2026 г.(ред.)

Ответ сгенерирован нейросетью и может содержать ошибки

Принцип подстановки Барбары Лисков (Liskov Substitution Principle)

Принцип подстановки Барбары Лисков (LSP) — один из пяти принципов SOLID, который гласит: объекты в программе должны быть заменяемы на объекты своих подтипов без нарушения корректности этой программы. Другими словами, если класс S является подтипом класса T, то объекты типа T в программе можно заменять объектами типа S без каких-либо нежелательных побочных эффектов.

Суть принципа

LSP требует, чтобы подклассы расширяли функциональность родителя, но не нарушали его контракт. Это означает:

  • Не ослабляй предусловия — подкласс не должен требовать больше, чем родитель
  • Не усиливай постусловия — подкласс не должен обещать меньше, чем родитель
  • Не выбрасывай новые исключения — которые не объявлены в родителе
  • Сохраняй инварианты — логические ограничения класса

Антипаттерн: нарушение LSP

// ❌ Плохо — нарушение LSP
public abstract class Bird
{
    public abstract void Fly();
}

public class Penguin : Bird
{
    public override void Fly()
    {
        throw new NotImplementedException("Пингвины не летают!");
    }
}

// Использование
Bird bird = new Penguin();
bird.Fly(); // Выброс исключения — нарушение контракта Bird

Проблема: клиент ожидает, что любая Bird может летать, но Penguin нарушает этот контракт.

Правильное решение

// ✅ Хорошо — правильная иерархия
public abstract class Bird
{
    public abstract void Move();
}

public class FlyingBird : Bird
{
    public override void Move()
    {
        Console.WriteLine("Летит");
    }
    
    public virtual void Fly()
    {
        // Реализация полета
    }
}

public class SwimmingBird : Bird
{
    public override void Move()
    {
        Console.WriteLine("Плывет");
    }
    
    public virtual void Swim()
    {
        // Реализация плавания
    }
}

public class Penguin : SwimmingBird
{
    public override void Move()
    {
        Swim();
    }
}

// Использование
Bird bird = new Penguin();
bird.Move(); // Работает корректно

Ещё один пример: иерархия фигур

// ❌ Нарушение LSP
public class Rectangle
{
    public virtual int Width { get; set; }
    public virtual int Height { get; set; }
    
    public virtual int GetArea() => Width * Height;
}

public class Square : Rectangle
{
    public override int Width
    {
        set { base.Width = base.Height = value; }
    }
    
    public override int Height
    {
        set { base.Width = base.Height = value; }
    }
}

// Проблема
Rectangle rect = new Square();
rect.Width = 5;
rect.Height = 10;
Console.WriteLine(rect.GetArea()); // 100 вместо 50

Правильный подход

// ✅ Правильно — композиция вместо наследования
public interface IShape
{
    int GetArea();
}

public class Rectangle : IShape
{
    public int Width { get; set; }
    public int Height { get; set; }
    
    public int GetArea() => Width * Height;
}

public class Square : IShape
{
    public int Side { get; set; }
    
    public int GetArea() => Side * Side;
}

Причины нарушения LSP

  • Неправильная иерархия классов — выбор неправильного родителя
  • Дополнительные ограничения — подкласс добавляет ограничения, которые родитель не имел
  • Разные поведение — подкласс кардинально меняет поведение методов
  • Невалидные состояния — подкласс не гарантирует инварианты родителя

Практическое значение

Solidный код, соответствующий LSP:

  • Предсказуем и безопасен для подстановки
  • Упрощает тестирование (полиморфизм работает как ожидается)
  • Облегчает расширение функционала
  • Предотвращает скрытые баги при использовании иерархий классов