Паттерн "Фабрика" в C# — когда хватает DI-контейнера
В порождающих паттернах описан канонический вариант GoF. Здесь фокус на современном .NET: где классическая абстрактная фабрика даёт структуру, а где ту же задачу проще решает контейнер зависимостей.
Что делает Abstract Factory
Абстрактная фабрика задаёт интерфейс для создания семейства связанных объектов без привязки клиента к конкретным классам.
Упрощённый пример для двух СУБД:
public interface IDbFactory
{
IDbConnection CreateConnection();
IDbCommand CreateCommand();
}
public class PostgresFactory : IDbFactory
{
public IDbConnection CreateConnection() => new NpgsqlConnection();
public IDbCommand CreateCommand() => new NpgsqlCommand();
}
public class SqlServerFactory : IDbFactory
{
public IDbConnection CreateConnection() => new SqlConnection();
public IDbCommand CreateCommand() => new SqlCommand();
}
Клиент получает IDbFactory и работает с контрактом:
public class OrderRepository
{
private readonly IDbFactory _factory;
public OrderRepository(IDbFactory factory) => _factory = factory;
public void Save(Order order)
{
using var connection = _factory.CreateConnection();
using var command = _factory.CreateCommand();
// ...
}
}
Переключение PostgreSQL/SQL Server выполняется заменой фабрики.
Цена классической схемы
Вместе с паттерном появляются дополнительные сущности:
- интерфейс фабрики;
- реализация под каждое семейство;
- новые методы при добавлении каждого типа продукта (
IDbParameter,IDbTransactionи т.д.).
Эта структура оправдана в больших доменах, где семейства объектов живут долго и реально расширяются. Для прикладного кода ASP.NET Core такая схема часто тяжеловесна по сравнению с возможностями DI-контейнера.
DI-контейнер решает ту же задачу
Контейнер зависимостей тоже выступает фабрикой: он создаёт и возвращает реализации по контракту.
Регистрация по окружению:
// Program.cs
if (builder.Environment.IsDevelopment())
{
builder.Services.AddScoped<IDbConnection, NpgsqlConnection>();
}
else
{
builder.Services.AddScoped<IDbConnection, SqlConnection>();
}
OrderRepository получает IDbConnection через конструктор. Переключение окружений сосредоточено в конфигурации приложения.
Для большинства сервисов это даёт тот же уровень развязки при меньшем количестве кода.
Выбор реализации в рантайме
Если решение зависит от входных данных (например, платёжный провайдер, выбранный пользователем), удобно использовать фабричный делегат:
builder.Services.AddScoped<Func<string, IPaymentProvider>>(sp => key => key switch
{
"stripe" => sp.GetRequiredService<StripeProvider>(),
"paypal" => sp.GetRequiredService<PayPalProvider>(),
_ => throw new ArgumentException($"Unknown provider: {key}")
});
В .NET 8 можно зарегистрировать keyed services:
builder.Services.AddKeyedScoped<IPaymentProvider, StripeProvider>("stripe");
builder.Services.AddKeyedScoped<IPaymentProvider, PayPalProvider>("paypal");
Получение keyed-сервиса:
public class CheckoutService
{
private readonly IServiceProvider _serviceProvider;
public CheckoutService(IServiceProvider serviceProvider)
{
_serviceProvider = serviceProvider;
}
public Task PayAsync(string userChoice)
{
var provider = _serviceProvider
.GetRequiredKeyedService<IPaymentProvider>(userChoice);
return provider.PayAsync();
}
}
Где Abstract Factory по-прежнему полезен
Паттерн остаётся практичным, когда:
| Ситуация | Почему фабрика полезна |
|---|---|
| Строгие семейства совместимых объектов | Один контракт создаёт набор, который согласован между собой |
| UI-темы и платформенные наборы | Кнопка, диалог и поля ввода берутся из одного style-пакета |
| Код вне DI-контейнера | Библиотеки, утилиты, изолированные модули и unit-тесты без host-пайплайна |
| Явный публичный API фреймворка | Контракт фабрики задаёт точку расширения для внешних плагинов |
Здесь фабрика даёт читаемую архитектуру и явную модель расширения.
Чек-лист выбора
| Вопрос | Выбор |
|---|---|
| Одна зависимость, выбор при старте приложения | Регистрация в DI |
| Несколько реализаций по ключу в рантайме | Делегат-фабрика или keyed services |
| Нужны согласованные семейства продуктов | Abstract Factory |
| Модуль работает автономно без контейнера | Abstract Factory или Factory Method |
| Паттерн увеличил слой абстракций без новых сценариев | Упростить до DI-конфигурации |
Для прикладных сервисов ASP.NET Core чаще хватает DI-конфигурации. Abstract Factory хорошо работает как инструмент архитектуры библиотек и платформенных слоёв с жёсткими семействами объектов.
Итог
Abstract Factory сохраняет ценность как паттерн проектирования. В современном C# многие прикладные сценарии покрываются DI-контейнером, фабричным делегатом и keyed services. Выигрыш приходит от выбора минимальной формы абстракции под конкретную задачу.
См. также
См. также
Другие статьи этого же раздела в боковом меню (как на странице "О разделе"). Паттерн — это повторяющийся шаблон, узор или схема. Паттерны встречаются повсюду — в природе, архитектуре, поведении людей и, конечно, в программировании. Порождающие паттерны проектирования — это группа шаблонов, направленных на решение задач, связанных с созданием объектов. Структурные паттерны — это группа шаблонов проектирования, решающих задачи организации классов и объектов таким образом, чтобы обеспечить гибкую архитектуру программного обеспечения. Поведенческие паттерны — это группа шаблонов проектирования, которые определяют способы взаимодействия объектов и распределения ответственности между ними. Архитектурные паттерны — это проверенные решения для организации структуры программного обеспечения. Интеграция систем — одна из центральных задач в современной разработке программного обеспечения. Паттерны доменного моделирования представляют собой проверенные решения для организации бизнес-логики в программных системах. Паттерн Strategy в C# — классическая реализация через интерфейс, замена на Func и Action, DI и критерии выбора без лишних абстракций. Паттерн Iterator в C# — ручной IEnumerator, генерация итератора компилятором через yield return, ленивость, LINQ и случаи, когда класс писать всё же нужно. Паттерн Command в C# — классическая схема, делегаты, MediatR, очередь задач, undo и критерии выбора между объектом команды и простым вызовом сервиса. Паттерн Observer в C# — event и делегаты, IObservable IObserver, слабая связанность, отписка и как не поймать утечки памяти в долгоживущих сервисах.Обзор паттернов проектирования
Порождающие паттерны
Структурные паттерны
Поведенческие паттерны
Архитектурные паттерны
Паттерны интеграции внешних систем
Паттерны проектирования доменных моделей
Стратегия в C#
Итератор в C#
Команда в C#
Наблюдатель в C#