Абстрактная фабрика (Abstract Factory)

Абстрактная фабрика создаёт набор связанных объектов через общий интерфейс. Клиент выбирает семейство один раз и затем не смешивает несовместимые реализации.

Когда возникает задача

Допустим, модуль метрик может работать в памяти или отдавать данные внешнему агенту. В обоих режимах нужны и счётчики, и таймеры. Если выбирать реализацию каждого объекта отдельно, легко случайно собрать несогласованный набор.

Сигналы:

  • одновременно создаются несколько видов продуктов;
  • продукты образуют семейства по платформе, окружению или протоколу;
  • смешивание представителей разных семейств недопустимо или бессмысленно;
  • клиентский код должен знать только интерфейсы продуктов.

Роли и взаимодействие

  • Abstract Factory объявляет методы для каждого вида продукта;
  • Concrete Factory создаёт одно согласованное семейство;
  • Abstract Products задают контракты отдельных участников;
  • Client получает одну фабрику и создаёт всё семейство только через неё.

Пример на Java

interface Counter { void increment(); }
interface Timer { void record(long millis); }
interface MetricsFactory {
    Counter counter(String name);
    Timer timer(String name);
}
final class InMemoryMetricsFactory implements MetricsFactory {
    public Counter counter(String name) { return () -> System.out.println(name + " +1"); }
    public Timer timer(String name) { return value -> System.out.println(name + "=" + value); }
}
final class NoOpMetricsFactory implements MetricsFactory {
    public Counter counter(String name) { return () -> {}; }
    public Timer timer(String name) { return value -> {}; }
}
final class Analyzer {
    private final Counter runs;
    private final Timer duration;
    Analyzer(MetricsFactory factory) {
        runs = factory.counter("analyzer.runs");
        duration = factory.timer("analyzer.duration");
    }
}

Analyzer не проверяет режим и не знает классы семейства. Конфигурационный слой создаёт нужную фабрику один раз.

Компромиссы

  • заменить целое семейство легко;
  • согласованность объектов задаётся конструкцией API;
  • добавить новое семейство относительно дёшево;
  • добавить новый вид продукта дорого: метод придётся добавить во все фабрики;
  • интерфейсы могут разрастись, поэтому иногда их делят на небольшие фабрики;
  • фабрика не гарантирует совместимость сама по себе — это обещание реализации и тестов.

Чем отличается

  • Фабричный метод Factory Method обычно создаёт один продукт и использует наследование; Abstract Factory создаёт семейство и чаще использует композицию;
  • Строитель Builder последовательно собирает один сложный объект;
  • Прототип Prototype создаёт вариант копированием уже настроенного экземпляра;
  • DI-контейнер может выступить механизмом сборки семейства, но сам паттерн остаётся виден в контрактах приложения.

Вопросы на собеседовании

  1. Что значит «семейство»? Набор продуктов, предназначенных работать в одном варианте окружения или реализации.
  2. Почему трудно добавить новый продукт? Меняется контракт фабрики и каждая её реализация.
  3. Нужны ли конкретной фабрике синглтоны? Нет; жизненный цикл не относится к определению паттерна.
  4. Можно ли реализовать фабрику лямбдами? Да, если продуктовые интерфейсы и количество методов позволяют сохранить понятный контракт.
  5. Где выбирать конкретную фабрику? В composition root — там, где приложение связывает конфигурацию с реализациями.

Связи: Порождающие паттерны, Принципы SOLID, Шаблоны проектирования.