Какие принципы SOLID ты знаешь и применяешь в Dart/Flutter?

«Какие принципы SOLID ты знаешь и применяешь в Dart/Flutter?» — вопрос из категории SOLID и принципы, который задают на 29% собеседований Flutter Разработчик. Ниже — развёрнутый ответ с разбором ключевых моментов.

Ответ

SOLID — это набор принципов объектно-ориентированного дизайна, которые я активно применяю для создания поддерживаемого кода на Dart.

  1. S (Single Responsibility): Класс должен иметь одну и только одну причину для изменения. В Flutter это означает разделение логики: Widget отвечает за отрисовку, а Bloc, ChangeNotifier или сервис — за бизнес-логику и состояние.

    // ПЛОХО: Виджет и логика загрузки данных вместе.
    // ХОРОШО: Виджет использует `UserRepository` или `UserCubit`, которые отвечают за получение данных.
  2. O (Open/Closed): Классы должны быть открыты для расширения, но закрыты для модификации. Достигается через использование абстракций (интерфейсов, абстрактных классов) и композиции.

    abstract class PaymentProcessor { Future<void> pay(double amount); }
    class StripeProcessor implements PaymentProcessor { ... }
    class PayPalProcessor implements PaymentProcessor { ... }
    // Класс `CheckoutService` зависит от `PaymentProcessor` и не меняется при добавлении новых способов оплаты.
  3. L (Liskov Substitution): Объекты подкласса должны быть заменяемы объектами родительского класса без нарушения работы программы. В Dart это строго контролируется системой типов.

    // Если `Bird` имеет метод `fly()`, то `Penguin`, не умеющий летать, не должен наследовать `Bird`.
    // Вместо этого можно создать более общий класс `Animal` или интерфейс `Walkable`.
  4. I (Interface Segregation): Много специализированных интерфейсов лучше одного общего. В Dart нет ключевого слова interface, но его роль выполняют абстрактные классы без реализации или обычные классы (контракт).

    // Вместо одного `Worker` с методами `work()`, `eat()`, `sleep()`.
    abstract class Workable { void work(); }
    abstract class Eatable { void eat(); }
    // Класс `Robot` реализует только `Workable`, а `Human` — оба интерфейса.
  5. D (Dependency Inversion): Зависимости должны строиться на абстракциях, а не на конкретных реализациях. Это основа для тестируемости и гибкой архитектуры (например, с использованием внедрения зависимостей).

    class UserService {
      final UserRepository repository; // Зависим от абстракции
      UserService(this.repository); // Внедрение через конструктор
      Future<User> getUser(int id) => repository.fetchUser(id);
    }
    // `UserRepository` может быть `ApiUserRepository`, `LocalUserRepository` или мок для тестов.

Применение этих принципов в Flutter особенно важно при использовании архитектур вроде Clean Architecture или при работе с состоянием (Provider, Riverpod, Bloc).

Видео-ответы