Приведи пример принципа подстановки
Комментарии (1)
Ответ сгенерирован нейросетью и может содержать ошибки
Принцип подстановки Лисков (LSP)
Принцип подстановки Лисков (Liskov Substitution Principle, LSP) — это третий из SOLID принципов объектно-ориентированного программирования, сформулированный Барбарой Лисков. Он гласит: объекты должны быть заменяемыми экземплярами своих базовых типов без нарушения корректности программы. Проще говоря, если класс S является подклассом класса T, то объекты типа T должны без проблем заменяться объектами типа S, не ломая логику программы.
Ключевые аспекты принципа
- Контракты предков и потомков: Подкласс не должен ужесточать предусловия (требования к входным данным) или ослаблять постусловия (гарантии на результат).
- Инварианты: Подкласс должен сохранять инварианты (внутренние условия целостности) базового класса.
- Семантическая совместимость: Поведение подкласса должно соответствовать ожиданиям, заданным базовым классом, а не только синтаксически (через сигнатуры методов).
Пример нарушения и соблюдения LSP в C#
Рассмотрим классический пример с геометрическими фигурами, который часто иллюстрирует нарушение LSP.
❌ Нарушение принципа подстановки
public class Rectangle
{
public virtual int Width { get; set; }
public virtual int Height { get; set; }
public int CalculateArea()
{
return 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; // Нарушение: аналогично
}
}
}
Проблема: Клиентский код, работающий с Rectangle, ожидает, что ширина и высота изменяются независимо. Например:
public void TestRectangleArea(Rectangle rect)
{
rect.Width = 5;
rect.Height = 4;
Console.WriteLine($"Ожидаемая площадь: 20, фактическая: {rect.CalculateArea()}");
// Для Rectangle: 5 * 4 = 20 (корректно)
// Для Square: 4 * 4 = 16 (нарушение ожиданий!)
}
Если передать в TestRectangleArea объект Square, результат будет неожиданным (16 вместо 20). Это нарушает LSP, так как Square не может быть заменой Rectangle без изменения поведения.
✅ Соблюдение принципа подстановки
Исправим пример, сделав Rectangle и Square независимыми классами, унаследованными от общего абстрактного базового класса:
public abstract class Shape
{
public abstract int CalculateArea();
}
public class Rectangle : Shape
{
public int Width { get; set; }
public int Height { get; set; }
public override int CalculateArea()
{
return Width * Height;
}
}
public class Square : Shape
{
public int SideLength { get; set; }
public override int CalculateArea()
{
return SideLength * SideLength;
}
}
Преимущества подхода:
- Классы
RectangleиSquareне связаны наследованием друг с другом, что устраняет проблему смены поведения. - Клиентский код работает с абстракцией
Shape, позволяя корректно заменять любые фигуры:
public void PrintArea(Shape shape)
{
Console.WriteLine($"Площадь: {shape.CalculateArea()}");
}
// Использование
var shapes = new List<Shape>
{
new Rectangle { Width = 5, Height = 4 },
new Square { SideLength = 4 }
};
foreach (var shape in shapes)
{
PrintArea(shape); // Работает корректно для всех фигур
}
Практическое применение LSP в Backend-разработке
В контексте C# Backend принцип Лисков критически важен для проектирования устойчивых архитектур:
- Интерфейсы и абстракции: Используйте интерфейсы для определения контрактов, которые гарантируют семантическую совместимость.
public interface IRepository<T>
{
T GetById(int id);
void Save(T entity);
}
public class UserRepository : IRepository<User>
{
public User GetById(int id) { /* реализация */ }
public void Save(User entity) { /* реализация */ }
}
public class CachedUserRepository : IRepository<User>
{
private readonly IRepository<User> _decoratedRepository;
public CachedUserRepository(IRepository<User> decoratedRepository)
{
_decoratedRepository = decoratedRepository;
}
public User GetById(int id)
{
// Проверка кэша, затем вызов _decoratedRepository.GetById()
// Поведение соответствует контракту IRepository
}
public void Save(User entity)
{
// Инвалидация кэша + вызов _decoratedRepository.Save()
}
}
-
Полиморфизм без сюрпризов: LSP обеспечивает корректную работу полиморфных коллекций и методов.
-
Тестируемость: Классы, соблюдающие LSP, легко тестировать через базовые интерфейсы.
Вывод
Принцип подстановки Лисков — это не просто правило наследования, а требование к семантической совместимости в иерархии классов. В Backend-разработке его соблюдение приводит к:
- Более стабильным API и контрактам
- Упрощению модульного тестирования
- Гибкости в внедрении зависимостей (DI)
- Устойчивости к изменениям через декораторы и стратегии
Нарушение LSP часто проявляется через проверки типа (is, as, GetType()) в клиентском коде — это явный "запах", указывающий на проблему в иерархии наследования.