TGViewer
Библиотека шарписта | C#, F#, .NET, ASP.NET Библиотека шарписта | C#, F#, .NET, ASP.NET @csharpproglib · 21.7K subscribers
Post #7078 2.44K
👨‍💻 Паттерн, который заменили DI-контейнером

Абстрактная фабрика даёт интерфейс для создания семейств связанных объектов без привязки к конкретным классам. В книге GoF это один из ключевых порождающих паттернов. В современном .NET он почти не нужен в классическом виде.

Допустим, приложение работает с разными базами данных. Для каждой нужны свои реализации подключения и команды:
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 и не знает, с какой базой работает. Переключение между PostgreSQL и SQL Server сводится к замене фабрики:
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();
// ...
}
}


Выглядит аккуратно. Но посмотрите, сколько кода нужно написать ещё до первой строки бизнес-логики. Интерфейс фабрики, две реализации, и это только для двух продуктов. Добавьте третий, например, IDbParameter и количество классов растёт линейно.

Почему DI-контейнер делает то же самое

DI-контейнер по своей сути и есть абстрактная фабрика. Вы регистрируете конкретные реализации, а контейнер разрешает их по интерфейсу.

Тот же пример без паттерна:
// Program.cs
if (builder.Environment.IsDevelopment())
{
builder.Services.AddScoped<IDbConnection, NpgsqlConnection>();
}
else
{
builder.Services.AddScoped<IDbConnection, SqlConnection>();
}


Никаких фабричных иерархий. Класс OrderRepository получает IDbConnection через конструктор и не знает, что за ним стоит. Переключение между окружениями (Development, Staging, Production) происходит в одном месте.

Когда нужен выбор в рантайме

Иногда реализацию нельзя определить при старте приложения. Например, пользователь выбирает способ оплаты, и от этого зависит, какой платёжный провайдер использовать. Для этого не нужна фабричная иерархия.

Достаточно фабричного делегата:
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");


Получение в конструкторе:
public class CheckoutService
{
private readonly IPaymentProvider _provider;

public CheckoutService(
[FromKeyedServices("stripe")] IPaymentProvider provider)
{
_provider = provider;
}
}


Или через IServiceProvider, если ключ известен только в рантайме:
var provider = serviceProvider
.GetRequiredKeyedService<IPaymentProvider>(userChoice);


Где абстрактная фабрика всё ещё уместна

Паттерн имеет смысл, когда нужно гарантировать совместимость объектов внутри семейства. Например, UI-фреймворк с темами, где кнопка, поле ввода и диалог должны принадлежать одному стилю. Фабрика не даст смешать Material-кнопку с Fluent-диалогом на уровне типов.

Ещё один случай. Код работает вне DI-контейнера. Библиотека, консольная утилита, unit-тесты с ручной подстановкой зависимостей. Там фабрика по-прежнему решает задачу.

Писать отдельную иерархию фабрик стоит только тогда, когда контейнер недоступен или когда важна строгая совместимость объектов внутри семейства.

➡️ Наша новостная подписка никогда не постареет

📍 Навигация: ВакансииЗадачиСобесы

🐸 Библиотека шарписта

#il_люминатор
  • 👍 10
More from @csharpproglib
  1. Sep 22, 2026💡 Replace, Regex или StringBuilder? Для замены текста в C# есть несколько инструментов. И…
  2. Sep 21, 2026⚙️ Настоящие атомарные операции Если Volatile решает проблему видимости, то Interlocked ре…
  3. Sep 20, 2026💪 Разминка перед трудовыми буднями Что произойдёт? ❤️ — список станет [1, 3] 🔥 — Invalid…
  4. Sep 19, 2026🧩 Middleware в ASP.NET Core: 3 ловушки Middleware — звено HTTP pipeline: app.Use(async (c…
  5. Sep 18, 2026📍 Навигация: Вакансии • Задачи • Собесы 🐸Библиотека шарписта #garbage_collector
  6. Sep 17, 2026🦆 Duck typing в C# — почти как в TypeScript В TypeScript достаточно, чтобы у объекта был…
Threads Profile ViewerView any public Threads profile without an account.Open ThreadLook →Writing with AI? Make it sound human.Metric37 rewrites AI drafts so they read naturally. Free AI detector, 1,500 words free.Try Metric37 →