CreationalC#verifiedVerified
Factory Method Pattern in C#
Defines an interface for creating an object but lets subclasses decide which class to instantiate. Defers instantiation to subclasses.
How to Implement the Factory Method Pattern in C#
1Step 1: Define the Product interface
public interface ITransport
{
string Deliver(string cargo);
}2Step 2: Concrete products
public class Truck : ITransport
{
public string Deliver(string cargo) =>
$"Delivering '{cargo}' by truck on road";
}
public class Ship : ITransport
{
public string Deliver(string cargo) =>
$"Delivering '{cargo}' by ship on sea";
}3Step 3: Creator declares the factory method
public abstract class Logistics
{
// Factory Method — subclasses decide which transport to create
protected abstract ITransport CreateTransport();
public string PlanDelivery(string cargo)
{
var transport = CreateTransport();
return transport.Deliver(cargo);
}
}4Step 4: Concrete creators override the factory method
public class RoadLogistics : Logistics
{
protected override ITransport CreateTransport() => new Truck();
}
public class SeaLogistics : Logistics
{
protected override ITransport CreateTransport() => new Ship();
}
// Usage:
// Logistics logistics = new RoadLogistics();
// Console.WriteLine(logistics.PlanDelivery("Electronics"));using Microsoft.Extensions.Logging;
// [step] Define the product interface with async support
public interface INotificationChannel : IAsyncDisposable
{
string ChannelType { get; }
Task<bool> SendAsync(Notification notification,
CancellationToken ct = default);
}
public record Notification(
string Recipient, string Subject, string Body,
Dictionary<string, string>? Metadata = null);
public record NotificationResult(
bool Success, string ChannelType, DateTime SentAt, string? Error = null);
// [step] Concrete products with real-world behavior
public class EmailChannel(ILogger<EmailChannel> logger) : INotificationChannel
{
public string ChannelType => "email";
public async Task<bool> SendAsync(
Notification notification, CancellationToken ct = default)
{
logger.LogInformation("Sending email to {Recipient}",
notification.Recipient);
await Task.Delay(100, ct); // Simulate SMTP call
return true;
}
public ValueTask DisposeAsync() => ValueTask.CompletedTask;
}
public class SmsChannel(ILogger<SmsChannel> logger) : INotificationChannel
{
public string ChannelType => "sms";
public async Task<bool> SendAsync(
Notification notification, CancellationToken ct = default)
{
logger.LogInformation("Sending SMS to {Recipient}",
notification.Recipient);
await Task.Delay(50, ct); // Simulate API call
return true;
}
public ValueTask DisposeAsync() => ValueTask.CompletedTask;
}
// [step] Abstract creator with template method
public abstract class NotificationService(ILogger logger)
{
protected abstract INotificationChannel CreateChannel();
public async Task<NotificationResult> NotifyAsync(
Notification notification, CancellationToken ct = default)
{
await using var channel = CreateChannel();
try
{
var success = await channel.SendAsync(notification, ct);
logger.LogInformation("Notification sent via {Channel}",
channel.ChannelType);
return new NotificationResult(success, channel.ChannelType,
DateTime.UtcNow);
}
catch (Exception ex)
{
logger.LogError(ex, "Failed to send via {Channel}",
channel.ChannelType);
return new NotificationResult(false, channel.ChannelType,
DateTime.UtcNow, ex.Message);
}
}
}
// [step] Concrete creators
public class EmailNotificationService(
ILogger<EmailNotificationService> logger,
ILogger<EmailChannel> channelLogger)
: NotificationService(logger)
{
protected override INotificationChannel CreateChannel() =>
new EmailChannel(channelLogger);
}
public class SmsNotificationService(
ILogger<SmsNotificationService> logger,
ILogger<SmsChannel> channelLogger)
: NotificationService(logger)
{
protected override INotificationChannel CreateChannel() =>
new SmsChannel(channelLogger);
}Factory Method Pattern Architecture
hourglass_empty
Rendering diagram...
lightbulb
Factory Method Pattern in the Real World
“Think of a logistics company that ships packages. The headquarters defines the shipping process but doesn’t decide the vehicle. Regional offices (subclasses) choose whether to use trucks, ships, or drones based on local conditions. The headquarters just says ‘get me a transport’ and the regional office delivers the right one.”