Что такое паттерн Абстрактная фабрика?

Ответ

Абстрактная фабрика — это порождающий паттерн проектирования, который предоставляет интерфейс для создания семейств связанных или зависимых объектов, не привязываясь к их конкретным классам.

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

Пример на Dart/Flutter: Допустим, мы создаем UI, который должен работать и на Android, и на iOS с нативным видом.

// Абстрактные продукты: элементы UI
abstract class Button {
  void render();
}
abstract class Switch {
  void toggle();
}

// Конкретные продукты для Material Design
class MaterialButton implements Button {
  @override
  void render() => print('Rendering a Material button');
}
class MaterialSwitch implements Switch {
  @override
  void toggle() => print('Toggling a Material switch');
}

// Конкретные продукты для Cupertino (iOS)
class CupertinoButton implements Button {
  @override
  void render() => print('Rendering a Cupertino button');
}
class CupertinoSwitch implements Switch {
  @override
  void toggle() => print('Toggling a Cupertino switch');
}

// Абстрактная фабрика
abstract class UIWidgetsFactory {
  Button createButton();
  Switch createSwitch();
}

// Конкретные фабрики
class MaterialFactory implements UIWidgetsFactory {
  @override
  Button createButton() => MaterialButton();
  @override
  Switch createSwitch() => MaterialSwitch();
}

class CupertinoFactory implements UIWidgetsFactory {
  @override
  Button createButton() => CupertinoButton();
  @override
  Switch createSwitch() => CupertinoSwitch();
}

// Клиентский код
class SettingsPage {
  SettingsPage(this.factory);
  final UIWidgetsFactory factory;

  void buildUI() {
    final button = factory.createButton();
    final switcher = factory.createSwitch();
    button.render();
    switcher.toggle();
  }
}

void main() {
  // В зависимости от платформы выбираем фабрику
  final factory = Platform.isAndroid ? MaterialFactory() : CupertinoFactory();
  final page = SettingsPage(factory);
  page.buildUI(); // Все элементы будут в одном стиле
}

Плюсы:

  • Гарантирует совместимость объектов из одного семейства.
  • Изолирует клиентский код от конкретных классов продуктов.
  • Упрощает поддержку кода и добавление новых семейств.

Минусы:

  • Сложность добавления нового типа продукта (например, Slider) — придется менять интерфейс фабрики и все её реализации.
  • Усложнение кода из-за большого количества классов.

Ответ 18+ 🔞

Давай разберемся, что за зверь такой — абстрактная фабрика. Представь, что ты заказываешь мебель. Ты можешь пойти в «Икею» и взять всё в стиле «минимализм»: стол, стул, шкаф — всё в одном дизайне. А можешь пойти в «Луи Филипп» и набрать себе барокко с позолотой. Суть в том, чтобы все предметы из одной коллекции подходили друг к другу, и ты случайно не поставил розовый стул из «Икеи» к дубовому столу «под старину». Вот эта «коллекция» — это и есть семейство объектов, а сама «Икея» или «Луи Филипп» — это и есть та самая фабрика.

Зачем это всё, ёпта? А затем, чтобы у тебя в приложении не возникла пиздопроебибна ситуация, когда на айфоне кнопки рисуются в стиле iOS, а переключатели — в стиле андроида. Гарантирует, что все виджеты будут из одного набора, и упрощает смену всего этого набора разом.

Смотри, как это в коде выглядит:

// Абстрактные продукты: типа, что вообще можно заказать — стул и стол.
abstract class Button {
  void render();
}
abstract class Switch {
  void toggle();
}

// Конкретные продукты из "Икеи" (Material Design)
class MaterialButton implements Button {
  @override
  void render() => print('Рисую плоскую материальную кнопку');
}
class MaterialSwitch implements Switch {
  @override
  void toggle() => print('Щёлкаю материальный свитч');
}

// Конкретные продукты из "Эппл-стора" (Cupertino)
class CupertinoButton implements Button {
  @override
  void render() => print('Рисую закруглённую купертиновскую кнопку');
}
class CupertinoSwitch implements Switch {
  @override
  void toggle() => print('Щёлкаю купертиновский свитч');
}

// Абстрактная фабрика — это каталог "Икеи" или "Эппл".
abstract class UIWidgetsFactory {
  Button createButton();
  Switch createSwitch();
}

// Сами фабрики, которые выдают товары из своего каталога.
class MaterialFactory implements UIWidgetsFactory {
  @override
  Button createButton() => MaterialButton();
  @override
  Switch createSwitch() => MaterialSwitch();
}

class CupertinoFactory implements UIWidgetsFactory {
  @override
  Button createButton() => CupertinoButton();
  @override
  Switch createSwitch() => CupertinoSwitch();
}

// Клиентский код — это ты, который заказывает мебель.
class SettingsPage {
  SettingsPage(this.factory);
  final UIWidgetsFactory factory;

  void buildUI() {
    final button = factory.createButton();
    final switcher = factory.createSwitch();
    button.render();
    switcher.toggle();
  }
}

void main() {
  // Смотришь, на какой ты платформе, и выбираешь каталог.
  final factory = Platform.isAndroid ? MaterialFactory() : CupertinoFactory();
  final page = SettingsPage(factory);
  page.buildUI(); // И всё, блядь, будет в одном стиле, без косяков.
}

Что хорошего?

  • Совместимость на уровне ядра. Все объекты из одного семейства точно подходят друг к другу. Доверия ебать ноль к случайным комбинациям.
  • Клиентский код не парится о конкретных классах. Ему всё равно, MaterialButton там или CupertinoButton. Он знает, что получит кнопку.
  • Хочешь сменить весь стиль? Один раз меняешь фабрику — и всё приложение переодевается. Красота.

Что плохого?

  • Хочешь добавить новый тип продукта? Например, слайдер. Ёперный театр! Придётся лезть в интерфейс абстрактной фабрики и пихать туда createSlider(), а потом обновлять ВСЕ её конкретные реализации. Овердохуища работы.
  • Классов становится, как говна за баней. На каждый чих — новый класс. Может превратиться в манду с ушами, если семейств и продуктов много.