Sobes.tech
Назад к вопросам
Junior — Middle
53

Можешь ли ты перечислить и пояснить основные принципы SOLID, применяемые при проектировании объектов?

Компании, где спрашивали
Бренд Монитор

Ответ от нейросети

sobes.tech AI

Что хотят услышать интервьюеры:

SOLID — это набор из пяти принципов объектно-ориентированного проектирования, которые помогают делать код проще для поддержки, расширения и тестирования. Обычно ожидают, что будут названы все пять принципов и коротко объяснено, какую проблему каждый решает. Важно показать не только определения, но и практический смысл: уменьшение связности, рост читаемости и снижение риска поломок при изменениях.

Определение:

SOLID — это аббревиатура из пяти принципов:

  • S — Single Responsibility Principle: у класса должна быть одна зона ответственности, то есть одна причина для изменения.
  • O — Open/Closed Principle: сущности должны быть открыты для расширения, но закрыты для модификации.
  • L — Liskov Substitution Principle: объекты наследников должны корректно подставляться вместо объектов базового типа.
  • I — Interface Segregation Principle: лучше несколько узких интерфейсов, чем один большой и универсальный.
  • D — Dependency Inversion Principle: зависимости должны строиться от абстракций, а не от конкретных реализаций.

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

Пример использования:

Допустим, есть система оформления заказов.
Если класс OrderService одновременно считает сумму, сохраняет заказ в базу и отправляет email, то он нарушает SRP. Лучше разделить это на отдельные компоненты: OrderCalculator, OrderRepository, EmailNotifier. Тогда при изменении способа отправки письма не придется трогать расчет заказа или сохранение.

public interface IOrderRepository
{
    void Save(Order order);
}

public interface INotifier
{
    void Notify(string message);
}

public class OrderCalculator
{
    public decimal CalculateTotal(Order order)
    {
        return order.Items.Sum(i => i.Price * i.Quantity);
    }
}

public class OrderService
{
    private readonly IOrderRepository _repository;
    private readonly INotifier _notifier;
    private readonly OrderCalculator _calculator;

    public OrderService(IOrderRepository repository, INotifier notifier, OrderCalculator calculator)
    {
        _repository = repository;
        _notifier = notifier;
        _calculator = calculator;
    }

    public void PlaceOrder(Order order)
    {
        var total = _calculator.CalculateTotal(order);
        _repository.Save(order);
        _notifier.Notify($"Order placed. Total: {total}");
    }
}

public class Order
{
    public List<OrderItem> Items { get; set; } = new();
}

public class OrderItem
{
    public decimal Price { get; set; }
    public int Quantity { get; set; }
}

Пояснение кода:

Код показывает разбиение ответственности по принципам SOLID.

OrderCalculator отвечает только за расчет суммы заказа — это пример SRP.
IOrderRepository и INotifier — абстракции, через которые OrderService работает с сохранением и уведомлением, а не с конкретными классами. Это отражает DIP.
Если потом нужно заменить email-уведомления на SMS или добавить логирование, достаточно реализовать новый INotifier, не меняя OrderService. Это уже связано с OCP: поведение расширяется через новые реализации, а не через переписывание существующего кода.

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

Ключевые моменты:

  • SRP: один класс — одна ответственность, иначе код быстро становится трудно сопровождать.
  • OCP: новое поведение лучше добавлять через новые классы и стратегии, а не править существующую логику.
  • LSP: наследники должны быть полноценной заменой базового типа без сюрпризов.
  • ISP: лучше дробить “толстые” интерфейсы на несколько маленьких и точных.
  • DIP: зависеть нужно от интерфейсов и абстракций, это снижает связность и упрощает тестирование.
  • SOLID особенно полезен в C# при работе с DI-контейнерами, сервисами и слоями приложения.