Что такое паттерн Фабричный метод?

Ответ

Фабричный метод — это порождающий паттерн, который определяет интерфейс (абстрактный метод) для создания объекта в базовом классе, но позволяет подклассам изменять тип создаваемого объекта. Таким образом, логика работы с продуктом остается в базовом классе, а конкретный класс продукта определяется наследниками.

Зачем он нужен? Чтобы отвязать основной код от конкретных классов создаваемых объектов. Это особенно полезно, когда у вас есть общая структура (виджет, диалог, обработчик), но конечная реализация может различаться в зависимости от контекста (платформа, настройки пользователя).

Пример на Dart/Flutter: Создаем диалоговое окно, внешний вид которого (кнопки) зависит от платформы.

// Абстрактный класс продукта - кнопка
abstract class DialogButton {
  String get label;
  void onPressed();
  Widget render(); // Возвращает Flutter-виджет
}

// Конкретные продукты
class MaterialDialogButton implements DialogButton {
  @override
  final String label;
  final VoidCallback onPressed;

  MaterialDialogButton({required this.label, required this.onPressed});

  @override
  Widget render() {
    return ElevatedButton(
      onPressed: onPressed,
      child: Text(label),
    );
  }
}

class CupertinoDialogButton implements DialogButton {
  @override
  final String label;
  final VoidCallback onPressed;

  CupertinoDialogButton({required this.label, required this.onPressed});

  @override
  Widget render() {
    return CupertinoButton(
      onPressed: onPressed,
      child: Text(label),
    );
  }
}

// Базовый класс Creator с фабричным методом
abstract class AlertDialog {
  // Фабричный метод - subclasses должны его реализовать
  DialogButton createConfirmButton();
  DialogButton createCancelButton();

  // Общая логика построения диалога, использующая фабричный метод
  Widget build(BuildContext context) {
    final confirmButton = createConfirmButton();
    final cancelButton = createCancelButton();

    // Здесь общая логика компоновки виджетов диалога...
    return Column(
      mainAxisSize: MainAxisSize.min,
      children: [
        // ... содержимое диалога ...
        Row(
          children: [
            cancelButton.render(),
            confirmButton.render(),
          ],
        ),
      ],
    );
  }
}

// Конкретные создатели (Concrete Creators)
class MaterialAlertDialog extends AlertDialog {
  final VoidCallback onConfirm;
  final VoidCallback onCancel;

  MaterialAlertDialog({required this.onConfirm, required this.onCancel});

  @override
  DialogButton createConfirmButton() {
    return MaterialDialogButton(
      label: 'OK',
      onPressed: onConfirm,
    );
  }

  @override
  DialogButton createCancelButton() {
    return MaterialDialogButton(
      label: 'Cancel',
      onPressed: onCancel,
    );
  }
}

class CupertinoAlertDialog extends AlertDialog {
  final VoidCallback onConfirm;
  final VoidCallback onCancel;

  CupertinoAlertDialog({required this.onConfirm, required this.onCancel});

  @override
  DialogButton createConfirmButton() {
    return CupertinoDialogButton(
      label: 'OK',
      onPressed: onConfirm,
    );
  }

  @override
  DialogButton createCancelButton() {
    return CupertinoDialogButton(
      label: 'Cancel',
      onPressed: onCancel,
    );
  }
}

// Использование:
// Widget build(BuildContext context) {
//   final dialog = Platform.isIOS
//       ? CupertinoAlertDialog(onConfirm: _save, onCancel: _close)
//       : MaterialAlertDialog(onConfirm: _save, onCancel: _close);
//   return dialog.build(context);
// }

Преимущества:

  • Избавляет от жесткой привязки к конкретным классам продуктов в основном коде.
  • Принцип единой ответственности: код создания продукта переносится в одно место (подклассы).
  • Принцип открытости/закрытости: легко добавить новый тип продукта, создав новый подкласс, не трогая существующий код.

Отличие от Абстрактной фабрики: Фабричный метод создает один тип продукта, а Абстрактная фабрика — целое семейство связанных продуктов.

Ответ 18+ 🔞

Давай разберем эту тему, чтобы в голове не осталось никакой каши. Фабричный метод — это, по сути, такая хитрая жопа в коде, которая говорит: «Э, сабака сука, не лезь ко мне с конкретными классами, я сам решу, какую штуку тебе отдать».

Если по-простому: есть у тебя базовый класс. В нём есть главный метод, который говорит: «Слушай, чувак, я тут всю основную работу сделаю, но конкретную деталь — кнопку там, или зверюгу какую — ты, наследничек, сделай сам». И ты в наследниках просто пишешь, какую именно кнопку создавать. А основная логика, где эта кнопка используется, уже готова и её не трогаешь. Волнение ебать, как удобно.

Зачем это всё? Чтобы твой основной код не превращался в свалку из if (platform.isIOS) ... else if (platform.isAndroid) .... Ты просто говоришь: «Создай мне диалог», а система сама, через фабричный метод, подсовывает нужные виджеты под платформу. Доверия ебать ноль к таким жестким связям, поэтому и придумали этот паттерн.

Смотри на примере, тут всё понятно станет. Делаем кнопки для диалога:

// Абстрактная кнопка — что она умеет делать
abstract class DialogButton {
  String get label;
  void onPressed();
  Widget render();
}

// Конкретная кнопка для Android-стиля (Material)
class MaterialDialogButton implements DialogButton {
  @override
  final String label;
  final VoidCallback onPressed;

  MaterialDialogButton({required this.label, required this.onPressed});

  @override
  Widget render() {
    return ElevatedButton(
      onPressed: onPressed,
      child: Text(label),
    );
  }
}

// Конкретная кнопка для iOS-стиля (Cupertino)
class CupertinoDialogButton implements DialogButton {
  @override
  final String label;
  final VoidCallback onPressed;

  CupertinoDialogButton({required this.label, required this.onPressed});

  @override
  Widget render() {
    return CupertinoButton(
      onPressed: onPressed,
      child: Text(label),
    );
  }
}

А теперь самое вкусное — создатель. Он знает КАК построить диалог, но не знает ИЗ ЧЕГО.

// Базовый класс для диалога. Он — главный постройщик.
abstract class AlertDialog {
  // Вот он, фабричный метод! Конкретной реализации тут нет.
  // Это как договорённость: "Наследник, ты обязан мне кнопки предоставить".
  DialogButton createConfirmButton();
  DialogButton createCancelButton();

  // А тут общая логика, которая использует эти абстрактные кнопки
  Widget build(BuildContext context) {
    final confirmButton = createConfirmButton(); // Создаётся где-то там, в наследнике
    final cancelButton = createCancelButton();

    return Column(
      mainAxisSize: MainAxisSize.min,
      children: [
        // ... какой-то контент диалога ...
        Row(
          children: [
            cancelButton.render(), // Используем как обычные виджеты
            confirmButton.render(),
          ],
        ),
      ],
    );
  }
}

И вот появляются конкретные ребята, которые знают, какие кнопки лепить:

// Диалог для Material (Android)
class MaterialAlertDialog extends AlertDialog {
  final VoidCallback onConfirm;
  final VoidCallback onCancel;

  MaterialAlertDialog({required this.onConfirm, required this.onCancel});

  @override
  DialogButton createConfirmButton() {
    // А вот и конкретика! Возвращаем Material-кнопку.
    return MaterialDialogButton(label: 'OK', onPressed: onConfirm);
  }

  @override
  DialogButton createCancelButton() {
    return MaterialDialogButton(label: 'Cancel', onPressed: onCancel);
  }
}

// Диалог для Cupertino (iOS)
class CupertinoAlertDialog extends AlertDialog {
  final VoidCallback onConfirm;
  final VoidCallback onCancel;

  CupertinoAlertDialog({required this.onConfirm, required this.onCancel});

  @override
  DialogButton createConfirmButton() {
    // А тут уже Cupertino-кнопка. Логика та же, продукт — другой.
    return CupertinoDialogButton(label: 'OK', onPressed: onConfirm);
  }

  @override
  DialogButton createCancelButton() {
    return CupertinoDialogButton(label: 'Cancel', onPressed: onCancel);
  }
}

Как этим пользоваться? Да проще пареной репы!

Widget build(BuildContext context) {
  // Выбираем, какую фабрику (наследника) использовать
  final dialog = Platform.isIOS
      ? CupertinoAlertDialog(onConfirm: _save, onCancel: _close)
      : MaterialAlertDialog(onConfirm: _save, onCancel: _close);
  // Всё! Остальная логика уже внутри.
  return dialog.build(context);
}

В чём соль, блядь?

  • Отвязка намерения от реализации: Твой главный код не забит проверками платформ. Он просто работает с абстрактной кнопкой. Ёперный театр, а не архитектура!
  • Всё по полочкам: Код создания кнопки живёт не где попало, а в своём уютном классе-наследнике.
  • Расширяемость — овердохуища: Захотел добавить диалог для веба? Создал новый WebAlertDialog, который возвращает HtmlDialogButton. Основной код AlertDialog.build() даже не чихнул. Ни хуя себе, правда?

Чем не является? Это НЕ Абстрактная фабрика. Фабричный метод — он про один продукт (кнопку). А Абстрактная фабрика — это когда у тебя целый набор связанных продуктов (кнопка, ползунок, чекбокс) для целой темы. Не путай, а то будет тебе хиросима и нигерсраки в архитектуре.