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

Какие знаешь принципы SOLID?

1.0 Junior🔥 181 комментариев
#Архитектура Flutter#ООП и паттерны

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

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

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

Какие знаешь принципы SOLID?

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

1. S — Single Responsibility Principle (Принцип единственной ответственности)

Класс должен иметь только одну причину для изменения. Каждый класс отвечает за одно.

Плохо:

class UserManager {
  // Слишком много ответственности
  void createUser(String name, String email) {
    // Валидация
    if (email.isEmpty) throw Exception("Email required");
    
    // Работа с БД
    database.insert("users", {"name": name, "email": email});
    
    // Отправка email
    emailService.send(email, "Welcome!");
    
    // Логирование
    logger.info("User $name created");
  }
}

Хорошо:

// Класс для валидации
class UserValidator {
  bool isValidEmail(String email) => email.contains("@");
}

// Класс для работы с БД
class UserRepository {
  void save(User user) {
    database.insert("users", user.toMap());
  }
}

// Класс для отправки уведомлений
class EmailService {
  void sendWelcomeEmail(String email) {
    sendEmail(email, "Welcome!");
  }
}

// Класс для логирования
class Logger {
  void logUserCreated(String name) {
    print("User $name created");
  }
}

// Оркестратор - использует все компоненты
class UserService {
  final UserValidator validator;
  final UserRepository repository;
  final EmailService emailService;
  final Logger logger;

  UserService(this.validator, this.repository, this.emailService, this.logger);

  void createUser(String name, String email) {
    if (!validator.isValidEmail(email)) {
      throw Exception("Invalid email");
    }
    
    var user = User(name, email);
    repository.save(user);
    emailService.sendWelcomeEmail(email);
    logger.logUserCreated(name);
  }
}

2. O — Open/Closed Principle (Принцип открытости/закрытости)

Классы открыты для расширения, но закрыты для модификации.

Плохо (модифицируем класс):

class PaymentProcessor {
  void processPayment(String type, double amount) {
    if (type == "credit_card") {
      // Логика для кредитной карты
    } else if (type == "paypal") {
      // Логика для PayPal
    } else if (type == "stripe") {
      // Логика для Stripe
      // При добавлении нового способа меняем класс!
    }
  }
}

Хорошо (расширяем через наследование):

abstract class PaymentMethod {
  Future<bool> process(double amount);
}

class CreditCardPayment extends PaymentMethod {
  @override
  Future<bool> process(double amount) async {
    // Логика для кредитной карты
    return true;
  }
}

class PayPalPayment extends PaymentMethod {
  @override
  Future<bool> process(double amount) async {
    // Логика для PayPal
    return true;
  }
}

class StripePayment extends PaymentMethod {
  @override
  Future<bool> process(double amount) async {
    // Логика для Stripe
    return true;
  }
}

// Новый способ - просто добавляем класс, ничего не меняя!
class ApplePayment extends PaymentMethod {
  @override
  Future<bool> process(double amount) async {
    return true;
  }
}

class PaymentProcessor {
  Future<bool> processPayment(PaymentMethod method, double amount) {
    return method.process(amount);
  }
}

3. L — Liskov Substitution Principle (Принцип подстановки Лисков)

Объекты подклассов должны корректно заменять объекты базовых классов.

Плохо (нарушение контракта):

class Bird {
  void fly() => print("Flying");
}

class Penguin extends Bird {
  @override
  void fly() {
    throw UnimplementedError("Penguins can't fly!");  // Нарушает контракт!
  }
}

void makeBirdFly(Bird bird) {
  bird.fly();  // Крах при передаче пингвина!
}

Хорошо (правильная иерархия):

abstract class Bird {
  void move();
}

class FlyingBird extends Bird {
  @override
  void move() => print("Flying");
}

class WalkingBird extends Bird {
  @override
  void move() => print("Walking");
}

class Penguin extends WalkingBird {
  @override
  void move() => print("Walking and swimming");
}

void moveBird(Bird bird) {
  bird.move();  // Всегда работает корректно
}

4. I — Interface Segregation Principle (Принцип разделения интерфейсов)

Клиенты не должны зависеть от интерфейсов, которые они не используют.

Плохо (большой интерфейс):

abstract class Worker {
  void work();
  void eat();
  void sleep();
  void payTaxes();
}

class Robot implements Worker {
  @override
  void work() => print("Working");
  
  @override
  void eat() => throw UnimplementedError();  // Robot не ест!
  
  @override
  void sleep() => throw UnimplementedError();  // Robot не спит!
  
  @override
  void payTaxes() => throw UnimplementedError();  // Robot не платит налоги!
}

Хорошо (раздельные интерфейсы):

abstract class Workable {
  void work();
}

abstract class Eatable {
  void eat();
}

abstract class Sleepable {
  void sleep();
}

class Human implements Workable, Eatable, Sleepable {
  @override
  void work() => print("Working");
  
  @override
  void eat() => print("Eating");
  
  @override
  void sleep() => print("Sleeping");
}

class Robot implements Workable {
  @override
  void work() => print("Working");
  // Только необходимый интерфейс!
}

5. D — Dependency Inversion Principle (Принцип инверсии зависимостей)

Высокоуровневые модули не должны зависеть от низкоуровневых. Оба должны зависеть от абстракций.

Плохо (прямые зависимости):

class EmailService {
  void sendEmail(String email, String message) {
    // Отправляем по SMTP
    print("Sending email to $email");
  }
}

class UserNotifier {
  final EmailService emailService = EmailService();  // Прямая зависимость!
  
  void notifyUser(String email) {
    emailService.sendEmail(email, "Hello!");
  }
}

Хорошо (инверсия через интерфейс):

abstract class NotificationService {
  Future<void> send(String recipient, String message);
}

class EmailService implements NotificationService {
  @override
  Future<void> send(String email, String message) async {
    print("Email to $email: $message");
  }
}

class SMSService implements NotificationService {
  @override
  Future<void> send(String phone, String message) async {
    print("SMS to $phone: $message");
  }
}

class UserNotifier {
  final NotificationService notificationService;
  
  // Инжекция зависимости через конструктор
  UserNotifier(this.notificationService);
  
  Future<void> notifyUser(String recipient) async {
    await notificationService.send(recipient, "Hello!");
  }
}

// Использование
void main() {
  // Легко менять реализацию без изменения UserNotifier
  var notifier1 = UserNotifier(EmailService());
  var notifier2 = UserNotifier(SMSService());
}

Практический пример в Flutter

// Абстракция
abstract class UserRepository {
  Future<List<User>> getUsers();
  Future<void> saveUser(User user);
}

// Реализация
class MockUserRepository implements UserRepository {
  @override
  Future<List<User>> getUsers() async => [User("John"), User("Jane")];
  
  @override
  Future<void> saveUser(User user) async {}
}

class ApiUserRepository implements UserRepository {
  @override
  Future<List<User>> getUsers() async {
    final response = await http.get(Uri.parse("api/users"));
    return parseUsers(response.body);
  }
  
  @override
  Future<void> saveUser(User user) async {
    await http.post(Uri.parse("api/users"), body: user.toJson());
  }
}

//ViewModel/provider
class UserViewModel extends ChangeNotifier {
  final UserRepository repository;
  List<User> users = [];
  
  UserViewModel(this.repository);
  
  Future<void> loadUsers() async {
    users = await repository.getUsers();
    notifyListeners();
  }
}

// Widget
class UserListWidget extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    // Инжекция зависимости
    return Provider(
      create: (_) => UserViewModel(ApiUserRepository()),
      child: Consumer<UserViewModel>(
        builder: (context, viewModel, _) {
          return ListView(
            children: viewModel.users
                .map((user) => ListTile(title: Text(user.name)))
                .toList(),
          );
        },
      ),
    );
  }
}

Таблица сравнения

Принцип  Суть                           Пример
─────────────────────────────────────────────────────
S        Одна ответственность           Отдельные классы для валидации, БД, уведомлений
O        Расширение без модификации     Использование абстрактных классов и наследования
L        Корректная подстановка         Правильная иерархия классов
I        Маленькие специфичные интерфейсы  Несколько интерфейсов вместо одного большого
D        Зависимость от абстракций      Инжекция зависимостей через конструктор

Когда применять SOLID

  • Для больших приложений — критично важно
  • В команде — облегчает код review и maintenance
  • Для тестирования — SOLID код легче тестировать
  • Для масштабирования — новые разработчики быстрее разбираются

Когда НЕ переусложнять

  • Маленькие скрипты — overkill
  • Прототипирование — сначала работает, потом рефакторит
  • Простые компоненты — если один класс делает всё, это OK

Принципы SOLID — это инвестиция в будущее проекта. Code написанный с их учётом значительно проще поддерживать, тестировать и расширять.