Приведи пример использования паттернов программирования в Dart/Flutter.

Ответ

1. State Management как реализация Observer (Наблюдатель): Пакет provider или riverpod — это классическая реализация паттерна Наблюдатель. Виджеты (наблюдатели) слушают изменения в объектах-состояниях (наблюдаемых).

// Пример с ChangeNotifier (наблюдаемый объект)
import 'package:flutter/material.dart';

class CounterModel with ChangeNotifier {
  int _count = 0;
  int get count => _count;

  void increment() {
    _count++;
    notifyListeners(); // Уведомляем всех слушателей (виджеты)
  }
}

// Виджет-слушатель (Consumer)
class MyCounterWidget extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return Column(
      children: [
        // Этот Text слушает изменения в CounterModel
        Consumer<CounterModel>(
          builder: (context, counter, child) => Text('Count: ${counter.count}'),
        ),
        ElevatedButton(
          onPressed: () => context.read<CounterModel>().increment(),
          child: Text('Increment'),
        ),
      ],
    );
  }
}

2. Factory Method для создания платформо-зависимых виджетов:

abstract class DialogButton {
  Widget build(BuildContext context);
}

class MaterialDialogButton implements DialogButton {
  @override
  Widget build(BuildContext context) {
    return TextButton(
      onPressed: () {},
      child: Text('OK'),
    );
  }
}

class CupertinoDialogButton implements DialogButton {
  @override
  Widget build(BuildContext context) {
    return CupertinoButton(
      onPressed: () {},
      child: Text('OK'),
    );
  }
}

// Фабричный метод
DialogButton createDialogButton(TargetPlatform platform) {
  switch (platform) {
    case TargetPlatform.android:
    case TargetPlatform.iOS:
      return CupertinoDialogButton();
    default:
      return MaterialDialogButton();
  }
}

3. Singleton для глобального доступа к сервисам:

class AnalyticsService {
  // Приватный конструктор
  AnalyticsService._privateConstructor();
  // Единственный экземпляр
  static final AnalyticsService _instance = AnalyticsService._privateConstructor();
  // Фабричный конструктор для доступа
  factory AnalyticsService() => _instance;

  void logEvent(String event) {
    // Отправка события в Firebase Analytics и т.д.
    print('Logged: $event');
  }
}

// Использование в любом месте приложения
AnalyticsService().logEvent('screen_opened');

Ответ 18+ 🔞

А, ну вот, опять про паттерны заговорили! Слушай, я тут подумал — Flutter-то, он же на самом деле просто набор этих самых паттернов, обёрнутых в красивые виджеты. Как будто конструктор «Лего», только для взрослых дядек, которые код пишут. Давай разберём, что к чему, а то некоторые до сих пор думают, что provider — это просто магия какая-то, а не классический Observer, ёпта.

1. State Management — это же просто Наблюдатель, ядрёна вошь! Вот смотри, берём provider или riverpod. Всё это — одна и та же песня, просто на новый лад. Есть объект-состояние (наш CounterModel), который орёт на всех углах: «Эй, я изменился!». А есть куча виджетов-подписчиков, которые его слушают и, как только услышат этот крик (notifyListeners()), сразу перерисовываются. Ничего нового, чистая классика, просто обёрнутая так, чтобы не выносила мозг.

// Это и есть наш "громкоговоритель" (Subject в Observer)
class CounterModel with ChangeNotifier {
  int _count = 0;
  int get count => _count;

  void increment() {
    _count++;
    notifyListeners(); // Кричит: "Бля, я обновился! Все ко мне!"
  }
}

// А это слушатели (Observers), которые ушами хлопают
class MyCounterWidget extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return Column(
      children: [
        // Consumer — это ухо, приложенное к громкоговорителю
        Consumer<CounterModel>(
          builder: (context, counter, child) => Text('Count: ${counter.count}'),
        ),
        // Кнопка, которая заставляет громкоговоритель кричать
        ElevatedButton(
          onPressed: () => context.read<CounterModel>().increment(),
          child: Text('Increment'),
        ),
      ],
    );
  }
}

Вот и вся магия. Никакой ебушки-воробушки, просто паттерн, которому сто лет в обед.

2. Factory Method — чтобы не париться с платформами. А это вообще любимый трюк, когда нужно, чтобы на айфоне кнопка была одна, а на андроиде — другая. Вместо того чтобы везде городить if (platform == ios), делаем фабрику. Она, как хитрая жопа, смотрит — на чём мы, и выдаёт нужную реализацию. Удобно, чё.

// Общий интерфейс для кнопки. Все должны уметь строить себя.
abstract class DialogButton {
  Widget build(BuildContext context);
}

// Конкретная кнопка под Material (Андроид)
class MaterialDialogButton implements DialogButton {
  @override
  Widget build(BuildContext context) {
    return TextButton(
      onPressed: () {},
      child: Text('OK'),
    );
  }
}

// Конкретная кнопка под Cupertino (iOS)
class CupertinoDialogButton implements DialogButton {
  @override
  Widget build(BuildContext context) {
    return CupertinoButton(
      onPressed: () {},
      child: Text('OK'),
    );
  }
}

// Сама фабрика — мозги, которые решают, что кому выдать
DialogButton createDialogButton(TargetPlatform platform) {
  switch (platform) {
    case TargetPlatform.android:
    case TargetPlatform.iOS:
      return CupertinoDialogButton(); // На iOS — стиль яблока
    default:
      return MaterialDialogButton(); // На всём остальном — Material
  }
}

И не нужно в каждом виджете голову ломать. Создал кнопку через фабрику — и похуй, где оно запустится.

3. Singleton — когда нужен один-единственный царь и бог. Ну а это, бля, классика жанра. Нужен тебе один сервис аналитики на всё приложение, чтобы не плодить сущностей. Делаем синглтон. Все знают, где он живёт, и все к нему ходят. Главное — не злоупотреблять, а то получится глобальная помойка, в которую все складывают свои дела, а потом разбираться — волнение ебать, где что лежит.

class AnalyticsService {
  // Прячем конструктор, чтобы никто не мог сделать новый экземпляр
  AnalyticsService._privateConstructor();
  // Вот он, наш единственный и неповторимый экземпляр
  static final AnalyticsService _instance = AnalyticsService._privateConstructor();
  // А это — дверь, через которую все к нему попадают
  factory AnalyticsService() => _instance;

  void logEvent(String event) {
    // Пишем логи куда надо
    print('Logged: $event');
  }
}

// Используем где угодно. Всегда получаем один и тот же объект.
AnalyticsService().logEvent('screen_opened');

Вот и всё. Никакой сложной науки, просто старые добрые паттерны в новом флатерном кафтане. Главное — понимать, что под капотом, а не просто копипастить код, как мартышлюшка.