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

Как применяются принципы SOLID во Flutter?

2.7 Senior🔥 172 комментариев
#Архитектура Flutter#ООП и паттерны

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

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

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

SOLID принципы во Flutter

SOLID — это набор пять принципов проектирования кода, которые делают его более масштабируемым и поддерживаемым. Во Flutter они применяются так же, как в любом ООП языке.

S — Single Responsibility (Одна ответственность)

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

Плохо — слишком много ответственности

class UserScreen extends StatefulWidget {
  @override
  State<UserScreen> createState() => _UserScreenState();
}

class _UserScreenState extends State<UserScreen> {
  // Это делает ВСЁ: UI, API запросы, логика бизнеса, парсинг
  
  Future<void> fetchUser(String id) async {
    final response = await http.get(Uri.parse(https://api.example.com/users/$id));
    if (response.statusCode == 200) {
      final json = jsonDecode(response.body);
      user = User.fromJson(json);
      // Бизнес логика здесь
      if (user.age < 18) {
        user.isAdult = false;
      }
      setState(() {});
    }
  }
  
  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(title: Text(User)),
      body: // построение UI
    );
  }
}

Хорошо — разделены ответственности

// Слой Domain — бизнес логика
class User {
  final String id;
  final String name;
  final int age;
  
  bool get isAdult => age >= 18;
}

// Слой Data — работа с API
class UserRepository {
  final http.Client httpClient;
  
  UserRepository(this.httpClient);
  
  Future<User> getUserById(String id) async {
    final response = await httpClient.get(Uri.parse(https://api.example.com/users/$id));
    if (response.statusCode == 200) {
      return User.fromJson(jsonDecode(response.body));
    } else {
      throw Exception(Failed to load user);
    }
  }
}

// Слой Application — use case
class FetchUserUseCase {
  final UserRepository repository;
  
  FetchUserUseCase(this.repository);
  
  Future<User> call(String id) => repository.getUserById(id);
}

// Слой Presentation — только UI
class UserScreen extends StatefulWidget {
  final FetchUserUseCase fetchUserUseCase;
  
  const UserScreen({required this.fetchUserUseCase});
  
  @override
  State<UserScreen> createState() => _UserScreenState();
}

class _UserScreenState extends State<UserScreen> {
  late User user;
  
  @override
  void initState() {
    super.initState();
    _loadUser();
  }
  
  Future<void> _loadUser() async {
    user = await widget.fetchUserUseCase(user_id);
    setState(() {});
  }
  
  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(title: Text(user.name)),
      body: Text(Age: ${user.age}, Adult: ${user.isAdult}),
    );
  }
}

O — Open/Closed (Открыт для расширения, закрыт для модификации)

Классы должны быть открыты для расширения (через наследование), но закрыты для модификации.

Плохо — нужно менять исходный код

// Когда добавляется новый тип платежа — нужно менять класс
class PaymentProcessor {
  void processPayment(String type, double amount) {
    if (type == credit_card) {
      // Логика для кредитной карты
    } else if (type == paypal) {
      // Логика для PayPal
    } else if (type == apple_pay) {
      // Логика для Apple Pay
    }
  }
}

Хорошо — расширяемо через полиморфизм

// Абстракция
abstract class PaymentMethod {
  Future<bool> processPayment(double amount);
}

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

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

class ApplePayPayment implements PaymentMethod {
  @override
  Future<bool> processPayment(double amount) async {
    // Логика для Apple Pay
    return true;
  }
}

// Обработчик — ЗАКРЫТ для изменений
class PaymentProcessor {
  final PaymentMethod _paymentMethod;
  
  PaymentProcessor(this._paymentMethod);
  
  Future<bool> process(double amount) => _paymentMethod.processPayment(amount);
}

// Использование
final creditCard = CreditCardPayment();
final processor = PaymentProcessor(creditCard);
await processor.process(100); // Добавить новый тип платежа — просто создать новый класс

L — Liskov Substitution (Подстановка Лисков)

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

Плохо — подкласс нарушает контракт

abstract class Bird {
  void fly(); // Контракт: все птицы летают
}

class Sparrow implements Bird {
  @override
  void fly() => print(Воробей летит);
}

class Penguin implements Bird {
  @override
  void fly() => throw Exception(Пингвины не летают); // Нарушение контракта!
}

Хорошо — правильная иерархия

abstract class Animal {
  void move();
}

class Bird extends Animal {
  @override
  void move() => print(Лечу);
}

class Sparrow extends Bird {}

class Penguin extends Animal {
  @override
  void move() => print(Плыву);
}

// Теперь можно подставлять любой Animal и не беспокоиться
void moveAnimal(Animal animal) {
  animal.move();
}

moveAnimal(Sparrow()); // OK
moveAnimal(Penguin()); // OK

I — Interface Segregation (Разделение интерфейсов)

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

Плохо — жирный интерфейс

abstract class Worker {
  void work();
  void eat();
  void sleep();
  void code(); // Не все рабочие кодят!
  void design(); // Не все рабочие дизайнят!
}

class Developer implements Worker {
  @override
  void work() => print(Кодю);
  
  @override
  void code() => print(Пишу код);
  
  @override
  void eat() => print(Ем);
  
  @override
  void sleep() => print(Сплю);
  
  @override
  void design() => throw UnimplementedError(); // Ненужный метод
}

Хорошо — тонкие интерфейсы

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

abstract class Coder {
  void code();
}

abstract class Designer {
  void design();
}

class Developer implements Worker, Coder {
  @override
  void work() => print(Кодю);
  
  @override
  void code() => print(Пишу код);
  
  @override
  void eat() => print(Ем);
  
  @override
  void sleep() => print(Сплю);
}

class UiDesigner implements Worker, Designer {
  @override
  void work() => print(Дизайню);
  
  @override
  void design() => print(Рисую интерфейсы);
  
  @override
  void eat() => print(Ем);
  
  @override
  void sleep() => print(Сплю);
}

D — Dependency Inversion (Инверсия зависимостей)

Зависимость должна быть от абстракций, а не от конкретных классов.

Плохо — прямая зависимость

class UserService {
  final UserDatabase database = UserDatabase(); // Жёсткая зависимость
  
  Future<User?> getUser(String id) {
    return database.getUser(id);
  }
}

class UserDatabase {
  Future<User?> getUser(String id) async {
    // SQL запрос
  }
}

Хорошо — инверсия через инъекцию

// Абстракция
abstract class UserDataSource {
  Future<User?> getUser(String id);
}

// Конкретная реализация
class SqliteUserDatabase implements UserDataSource {
  @override
  Future<User?> getUser(String id) async {
    // SQLite запрос
  }
}

class FirebaseUserDatabase implements UserDataSource {
  @override
  Future<User?> getUser(String id) async {
    // Firebase запрос
  }
}

// Сервис зависит от АБСТРАКЦИИ
class UserService {
  final UserDataSource dataSource;
  
  UserService(this.dataSource); // Инъекция
  
  Future<User?> getUser(String id) => dataSource.getUser(id);
}

// Использование — легко переключать реализации
final sqliteDb = SqliteUserDatabase();
final userService = UserService(sqliteDb);

// Или
final firebaseDb = FirebaseUserDatabase();
final userService2 = UserService(firebaseDb); // Тестирование легче

Практический пример: Полное приложение с SOLID

// Domain — бизнес логика
class Post {
  final int id;
  final String title;
  final String content;
  
  Post({required this.id, required this.title, required this.content});
}

// Data — абстракция источника
abstract class PostDataSource {
  Future<List<Post>> getPosts();
}

// Конкретная реализация
class ApiPostDataSource implements PostDataSource {
  @override
  Future<List<Post>> getPosts() async {
    final response = await http.get(Uri.parse(https://api.example.com/posts));
    if (response.statusCode == 200) {
      final List json = jsonDecode(response.body);
      return json.map((p) => Post(
        id: p[id],
        title: p[title],
        content: p[content],
      )).toList();
    }
    throw Exception(Failed to load posts);
  }
}

// Application
class GetPostsUseCase {
  final PostDataSource dataSource;
  
  GetPostsUseCase(this.dataSource);
  
  Future<List<Post>> call() => dataSource.getPosts();
}

// Presentation
class PostsScreen extends StatefulWidget {
  final GetPostsUseCase getPostsUseCase;
  
  const PostsScreen({required this.getPostsUseCase});
  
  @override
  State<PostsScreen> createState() => _PostsScreenState();
}

class _PostsScreenState extends State<PostsScreen> {
  late Future<List<Post>> _postsFuture;
  
  @override
  void initState() {
    super.initState();
    _postsFuture = widget.getPostsUseCase();
  }
  
  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(title: Text(Posts)),
      body: FutureBuilder<List<Post>>(
        future: _postsFuture,
        builder: (context, snapshot) {
          if (snapshot.hasData) {
            return ListView.builder(
              itemCount: snapshot.data!.length,
              itemBuilder: (context, index) {
                final post = snapshot.data![index];
                return ListTile(
                  title: Text(post.title),
                  subtitle: Text(post.content),
                );
              },
            );
          }
          return Center(child: CircularProgressIndicator());
        },
      ),
    );
  }
}

Проверка SOLID в коде

Прежде чем сдавать код:

  • S — каждый класс решает одну задачу?
  • O — можно добавить новую функцию без изменения старого кода?
  • L — подклассы безопасно заменяют супер-классы?
  • I — классы не зависят от ненужных методов?
  • D — зависимости инъектируются, а не жёстко закодированы?

Вывод: SOLID — это основа масштабируемого кода. Во Flutter особенно важны S (разделение), O (полиморфизм), D (инъекция). Используй абстракции (abstract classes/interfaces) и слойную архитектуру.