Абстрактная фабрика (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-контейнер может выступить механизмом сборки семейства, но сам паттерн остаётся виден в контрактах приложения.
Вопросы на собеседовании
- Что значит «семейство»? Набор продуктов, предназначенных работать в одном варианте окружения или реализации.
- Почему трудно добавить новый продукт? Меняется контракт фабрики и каждая её реализация.
- Нужны ли конкретной фабрике синглтоны? Нет; жизненный цикл не относится к определению паттерна.
- Можно ли реализовать фабрику лямбдами? Да, если продуктовые интерфейсы и количество методов позволяют сохранить понятный контракт.
- Где выбирать конкретную фабрику? В composition root — там, где приложение связывает конфигурацию с реализациями.
Связи: Порождающие паттерны, Принципы SOLID, Шаблоны проектирования.