Перейти к основному содержимому

Паттерн "Фабрика" в 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 выполняется заменой фабрики.

Загрузка ArchiStyler…

Цена классической схемы

Вместе с паттерном появляются дополнительные сущности:

  • интерфейс фабрики;
  • реализация под каждое семейство;
  • новые методы при добавлении каждого типа продукта (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-конфигурации
Практика в .NET

Для прикладных сервисов ASP.NET Core чаще хватает DI-конфигурации. Abstract Factory хорошо работает как инструмент архитектуры библиотек и платформенных слоёв с жёсткими семействами объектов.


Итог

Abstract Factory сохраняет ценность как паттерн проектирования. В современном C# многие прикладные сценарии покрываются DI-контейнером, фабричным делегатом и keyed services. Выигрыш приходит от выбора минимальной формы абстракции под конкретную задачу.


См. также

См. также

Другие статьи этого же раздела в боковом меню (как на странице "О разделе").