Back to blog
Backend Systemsbeginner

Factory Method and Abstract Factory in C#

Learn Factory Method and Abstract Factory patterns in C#: create objects without specifying exact types, support extensibility, and decouple construction from usage.

Asma HafeezApril 17, 20264 min read
csharpdesign-patternsfactorydotnetoop
Share:𝕏

Factory Method and Abstract Factory

Factory patterns decouple the creation of objects from their usage. Instead of new ConcreteClass(), you ask a factory for an instance — the factory decides what to create.


Why Factories?

C#
// Without factory — tightly coupled to concrete type
var notifier = new EmailNotifier();  // what if we need Slack? SMS? Push?
notifier.Send("Order shipped!");

// With factory — decoupled
var notifier = notifierFactory.Create(user.Preferences);
notifier.Send("Order shipped!");  // factory decides which type

Factory Method

A method (or class) that creates objects. Subclasses override it to change what gets created.

C#
public interface INotifier
{
    void Send(string message, string recipient);
}

public class EmailNotifier : INotifier
{
    public void Send(string message, string recipient)
        => Console.WriteLine($"Email to {recipient}: {message}");
}

public class SmsNotifier : INotifier
{
    public void Send(string message, string recipient)
        => Console.WriteLine($"SMS to {recipient}: {message}");
}

public class SlackNotifier : INotifier
{
    public void Send(string message, string recipient)
        => Console.WriteLine($"Slack to #{recipient}: {message}");
}

// Factory method — static factory
public static class NotifierFactory
{
    public static INotifier Create(NotificationType type) => type switch
    {
        NotificationType.Email => new EmailNotifier(),
        NotificationType.Sms   => new SmsNotifier(),
        NotificationType.Slack => new SlackNotifier(),
        _ => throw new ArgumentException($"Unknown type: {type}")
    };
}

// Usage
var notifier = NotifierFactory.Create(user.PreferredNotification);
notifier.Send("Your order has shipped!", user.Contact);

Registration-Based Factory (Open/Closed)

The switch statement above requires modifying the factory to add new types. A registration-based factory is open for extension.

C#
public class NotifierFactory
{
    private readonly Dictionary<NotificationType, Func<INotifier>> _registry = new();

    public void Register(NotificationType type, Func<INotifier> creator)
        => _registry[type] = creator;

    public INotifier Create(NotificationType type)
    {
        if (!_registry.TryGetValue(type, out var creator))
            throw new KeyNotFoundException($"No notifier for {type}");
        return creator();
    }
}

// At startup — register all types
var factory = new NotifierFactory();
factory.Register(NotificationType.Email, () => new EmailNotifier());
factory.Register(NotificationType.Sms,   () => new SmsNotifier());
// Adding a new type: just register it — no factory code changes!
factory.Register(NotificationType.Slack, () => new SlackNotifier());

Factory with DI in ASP.NET Core

C#
// Register multiple implementations
builder.Services.AddScoped<EmailNotifier>();
builder.Services.AddScoped<SmsNotifier>();
builder.Services.AddScoped<SlackNotifier>();

// Factory that uses DI to create instances
builder.Services.AddScoped<INotifierFactory, NotifierFactory>();

public class NotifierFactory(IServiceProvider provider) : INotifierFactory
{
    public INotifier Create(NotificationType type) => type switch
    {
        NotificationType.Email => provider.GetRequiredService<EmailNotifier>(),
        NotificationType.Sms   => provider.GetRequiredService<SmsNotifier>(),
        NotificationType.Slack => provider.GetRequiredService<SlackNotifier>(),
        _ => throw new ArgumentException($"Unknown type: {type}")
    };
}

Abstract Factory — Families of Objects

Abstract Factory creates families of related objects without specifying concrete types. Use it when objects must be compatible with each other.

C#
// Abstract factory — creates a family of UI components
public interface IUiFactory
{
    IButton CreateButton();
    ITextField CreateTextField();
    IDialog CreateDialog();
}

// Concrete factory for each theme/platform
public class MaterialUiFactory : IUiFactory
{
    public IButton    CreateButton()    => new MaterialButton();
    public ITextField CreateTextField() => new MaterialTextField();
    public IDialog    CreateDialog()    => new MaterialDialog();
}

public class WindowsUiFactory : IUiFactory
{
    public IButton    CreateButton()    => new WindowsButton();
    public ITextField CreateTextField() => new WindowsTextField();
    public IDialog    CreateDialog()    => new WindowsDialog();
}

// Application uses the abstract factory — doesn't know which theme
public class FormBuilder(IUiFactory factory)
{
    public Form Build()
    {
        var button    = factory.CreateButton();
        var textField = factory.CreateTextField();
        return new Form(button, textField);  // all components from the same family
    }
}

Real-World: Notification Strategy

C#
public class NotificationService(INotifierFactory factory)
{
    public async Task NotifyAsync(User user, string message)
    {
        var notifier = factory.Create(user.PreferredChannel);
        notifier.Send(message, user.ContactInfo);

        // Also notify via secondary channel if configured
        if (user.SecondaryChannel.HasValue)
        {
            var secondary = factory.Create(user.SecondaryChannel.Value);
            secondary.Send(message, user.SecondaryContact!);
        }
    }
}

Key Takeaways

  1. Factory Method encapsulates new — callers get an instance without knowing the concrete type
  2. Use a registration-based factory to follow Open/Closed — add new types without modifying the factory
  3. Abstract Factory ensures that a family of objects (all Material, all Windows) are compatible
  4. In ASP.NET Core, use IServiceProvider in factories to get DI-resolved instances
  5. The goal is decoupling — the caller shouldn't need to know what it's creating or how

Enjoyed this article?

Explore the Backend Systems learning path for more.

Found this helpful?

Share:𝕏

Leave a comment

Have a question, correction, or just found this helpful? Leave a note below.