Factory, Strategy and Adapter patterns in C#: keyed services and an ERP anti-corruption layer

.NET Architecture & Design Patterns November 13, 2026 csharp

Strategy + Factory: each courier is a strategy (Q18).

csharp
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
// ======================================================================
// Q: Show me Factory, Strategy and Adapter in a real scenario.
//
// Strategy + Factory: each courier is a strategy (Q18). The factory picks the
// right one at runtime, here with keyed services (.NET 8).
// Adapter: translates a foreign interface (the ERP SDK) into ours. This is also
// the anti-corruption layer.
// ======================================================================

// Strategy + Factory with keyed services (.NET 8)
builder.Services.AddKeyedScoped<IShippingCalculator, DhlCalculator>("dhl");
builder.Services.AddKeyedScoped<IShippingCalculator, UpsCalculator>("ups");
builder.Services.AddScoped<IShippingCalculatorFactory, ShippingCalculatorFactory>();

public class ShippingCalculatorFactory(IServiceProvider sp) : IShippingCalculatorFactory
{
    // IServiceProvider is allowed here: the factory is infrastructure, not business logic
    public IShippingCalculator Create(string carrier) =>
        sp.GetKeyedService<IShippingCalculator>(carrier)
        ?? throw new NotSupportedException($"Carrier '{carrier}' is not supported");
}

// Usage in an endpoint: [FromKeyedServices] when the key is known at compile time
app.MapGet("/quote/dhl", ([FromKeyedServices("dhl")] IShippingCalculator calc) => /* ... */);

// Adapter: ERP SDK behind our own interface (anti-corruption layer)
public class SapErpAdapter(SapClient sap) : IErpClient
{
    public Task SendOrderAsync(Order order, CancellationToken ct)
    {
        var sapOrder = new SapSalesOrder
        {
            KUNNR = order.CustomerId.ToString(),            // SAP field names stay in here
            ITEMS = order.Lines.Select(l => new SapItem { MATNR = l.Sku, KWMENG = l.Quantity }).ToList()
        };
        return sap.CreateSalesOrderAsync(sapOrder, ct);
    }
}