Перейти к содержанию

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

Категория: порождающий паттерн.

Проблема

Приложению нужно создавать не один объект, а согласованное семейство связанных объектов - например, набор UI-контролов одной темы (кнопка, чекбокс, поле ввода) или набор компонентов под конкретную СУБД (соединение, команда, адаптер). При этом важно гарантировать, что объекты из разных семейств никогда не будут случайно смешаны (светлая кнопка с тёмным чекбоксом, MySQL-соединение с PostgreSQL-командой).

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

Решение

  1. Для каждого вида продукта в семействе выделяется абстракция (IButton, ICheckbox).
  2. Вводится интерфейс абстрактной фабрики с одним методом создания на каждый вид продукта (CreateButton(), CreateCheckbox()).
  3. На каждое семейство пишется своя конкретная фабрика (LightUiFactory, DarkUiFactory), которая создаёт продукты именно этого семейства.
  4. Клиентский код получает одну конкретную фабрику (обычно через DI или конфигурацию) и создаёт через неё все нужные объекты - гарантированно из одного семейства.

Структура

  • AbstractProductA, AbstractProductB - абстракции продуктов.
  • ConcreteProductA1/A2, ConcreteProductB1/B2 - реализации продуктов для разных семейств.
  • IAbstractFactory - интерфейс с методом на каждый вид продукта.
  • ConcreteFactory1/2 - конкретные фабрики, создающие продукты одного семейства.
  • Client - работает только с абстракциями IAbstractFactory, AbstractProductA/B.

Когда применять

  • В системе есть несколько семейств взаимосвязанных продуктов, и важно гарантировать, что используется ровно одно семейство за раз.
  • Нужно легко переключать целое семейство реализаций (тема оформления, СУБД, облачный провайдер) в одном месте, не меняя клиентский код.

Плюсы

  • Гарантирует совместимость продуктов внутри одного семейства.
  • Изолирует клиентский код от конкретных классов продуктов.
  • Новое семейство добавляется новой фабрикой, без изменения существующего кода (открытость/закрытость).

Минусы

  • Добавление нового вида продукта (например, IRadioButton в дополнение к кнопке и чекбоксу) требует изменения интерфейса фабрики и всех её реализаций - это нарушает открытость/закрытость в другом направлении.
  • Больше кода и абстракций, чем при прямом создании объектов - оправдано только при реальном наличии нескольких семейств.

Отличие от Factory Method

Abstract Factory обычно реализуется через несколько Factory Method - каждый метод фабрики (CreateButton, CreateCheckbox) сам по себе является фабричным методом. Разница в масштабе: Factory Method - это точка расширения в одном классе для одного продукта, Abstract Factory - самостоятельный объект, отвечающий за целое семейство продуктов.

Пример в .NET Framework / BCL

  • System.Data.Common.DbProviderFactory - абстрактная фабрика для ADO.NET: конкретные реализации (SqlClientFactory, NpgsqlFactory) создают согласованное семейство DbConnection, DbCommand, DbDataAdapter для конкретной СУБД.
  • Фабрики контролов в кросс-платформенных UI-фреймворках, где один "движок рендеринга" должен создавать визуально согласованный набор элементов управления.

Пример реализации на C#

AbstractFactory.cs
using System;

namespace DesignPatterns.Creational.AbstractFactory
{
    // Семейство продуктов №1
    public interface IButton
    {
        void Render();
    }

    // Семейство продуктов №2
    public interface ICheckbox
    {
        void Render();
    }

    // Конкретные продукты для темы "Light"
    public sealed class LightButton : IButton
    {
        public void Render() => Console.WriteLine("Светлая кнопка");
    }

    public sealed class LightCheckbox : ICheckbox
    {
        public void Render() => Console.WriteLine("Светлый чекбокс");
    }

    // Конкретные продукты для темы "Dark"
    public sealed class DarkButton : IButton
    {
        public void Render() => Console.WriteLine("Тёмная кнопка");
    }

    public sealed class DarkCheckbox : ICheckbox
    {
        public void Render() => Console.WriteLine("Тёмный чекбокс");
    }

    // Абстрактная фабрика - гарантирует, что кнопка и чекбокс будут из одной темы
    public interface IUiFactory
    {
        IButton CreateButton();
        ICheckbox CreateCheckbox();
    }

    public sealed class LightUiFactory : IUiFactory
    {
        public IButton CreateButton() => new LightButton();
        public ICheckbox CreateCheckbox() => new LightCheckbox();
    }

    public sealed class DarkUiFactory : IUiFactory
    {
        public IButton CreateButton() => new DarkButton();
        public ICheckbox CreateCheckbox() => new DarkCheckbox();
    }

    // Клиентский код работает только с абстракциями и никогда не смешивает
    // компоненты из разных семейств (например, светлую кнопку с тёмным чекбоксом).
    public sealed class SettingsDialog
    {
        private readonly IButton _button;
        private readonly ICheckbox _checkbox;

        public SettingsDialog(IUiFactory factory)
        {
            _button = factory.CreateButton();
            _checkbox = factory.CreateCheckbox();
        }

        public void Render()
        {
            _button.Render();
            _checkbox.Render();
        }
    }

    public static class Demo
    {
        public static void Run()
        {
            IUiFactory factory = DateTime.Now.Hour < 18
                ? new LightUiFactory()
                : new DarkUiFactory();

            var dialog = new SettingsDialog(factory);
            dialog.Render();
        }
    }
}

Открыть AbstractFactory.cs отдельно Скачать AbstractFactory.cs