«Factory» ist im Alltag ein Sammelbegriff für drei verwandte Dinge. Sie unterscheiden sich darin, wer entscheidet.
Simple Factory: eine Klasse entscheidet
Die häufigste Variante — und die, die im Buch der Gang of Four gar nicht steht. Eine Klasse nimmt einen Schlüssel und gibt ein fertiges Objekt hinter einer Schnittstelle zurück.
public interface IBenachrichtigung
{
void Sende(string empfaenger, string text);
}
public sealed class BenachrichtigungsFactory
{
private readonly IServiceProvider _services;
public BenachrichtigungsFactory(IServiceProvider services) => _services = services;
public IBenachrichtigung Erzeuge(Kanal kanal) => kanal switch
{
Kanal.Email => _services.GetRequiredService<EmailBenachrichtigung>(),
Kanal.Sms => _services.GetRequiredService<SmsBenachrichtigung>(),
Kanal.Push => _services.GetRequiredService<PushBenachrichtigung>(),
_ => throw new ArgumentOutOfRangeException(nameof(kanal), kanal, "Unbekannter Kanal"),
};
}Ja, da ist wieder ein switch
Und das ist in Ordnung. Eine Factory schafft die Verzweigung nicht ab — sie sorgt dafür, dass es sie genau einmal gibt, an einer Stelle, deren erklärte Aufgabe das Auswählen ist. Vorher stand dieselbe Entscheidung in jedem Aufrufer.
Der Aufrufer sieht davon nichts mehr:
public void BestellungAbschliessen(Bestellung bestellung)
{
var kanal = _kunden.LeseKanal(bestellung.KundenId); // erst jetzt bekannt
var kurier = _factory.Erzeuge(kanal);
kurier.Sende(bestellung.Kontakt, "Danke für deine Bestellung");
}Factory Method: die Unterklasse entscheidet
Das eigentliche GoF-Pattern. Eine Basisklasse beschreibt einen Ablauf und lässt genau einen Schritt offen: das Erzeugen. Welche konkrete Klasse entsteht, legt die Unterklasse fest.
public abstract class Exportlauf
{
// Der Ablauf steht hier — und nur hier.
public async Task AusfuehrenAsync(IReadOnlyList<Bestellung> bestellungen)
{
await using var schreiber = ErzeugeSchreiber(); // die Factory Method
await schreiber.KopfAsync();
foreach (var b in bestellungen)
await schreiber.ZeileAsync(b);
await schreiber.AbschlussAsync();
}
protected abstract IExportSchreiber ErzeugeSchreiber();
}
public sealed class CsvExport : Exportlauf
{
protected override IExportSchreiber ErzeugeSchreiber() => new CsvSchreiber("export.csv");
}
public sealed class JsonExport : Exportlauf
{
protected override IExportSchreiber ErzeugeSchreiber() => new JsonSchreiber("export.json");
}Der Unterschied in einem Satz
Bei der Simple Factory fragst du eine Factory nach einem Objekt. Bei der Factory Method erbt jemand von dir und beantwortet die Frage, welches Objekt dein eigener Ablauf verwenden soll.
Abstract Factory: Familien, die zusammenpassen müssen
Die dritte Variante, und die seltenste. Sie erzeugt nicht ein Objekt, sondern einen Satz von Objekten, die zueinander passen müssen.
public interface IPersistenzFactory
{
IVerbindung ErzeugeVerbindung();
IAbfrageUebersetzer ErzeugeUebersetzer();
ITransaktion ErzeugeTransaktion();
}
public sealed class PostgresFactory : IPersistenzFactory { /* ... */ }
public sealed class SqlServerFactory : IPersistenzFactory { /* ... */ }Der Sinn ist die Garantie: Wer die PostgresFactory hat, kann keinen SQL-Server-Übersetzer an eine Postgres-Verbindung hängen. Die Familie kommt als Ganzes oder gar nicht.
Wenn deine drei Objekte auch einzeln beliebig kombinierbar wären, brauchst du keine Abstract Factory — dann sind es drei normale Registrierungen.
Kurz nachgedacht
Zählt nicht — nur zum Prüfen, ob es angekommen ist.
1.Eine Factory enthält einen switch über den Kanal. Ist damit nicht nur das Problem umgezogen?