Diese switch-Anweisung ist ein Anti-Pattern; Erwägen Sie stattdessen die Verwendung von Polymorphismus.
Das hat mich zum Nachdenken gebracht: Warum wird von „Switch-Statement-Polymorphismus“ im modernen Softwaredesign abgeraten? Wenn Sie C# (oder eine andere objektorientierte Sprache) geschrieben haben, sind Sie möglicherweise auf einen ähnlichen Vorschlag gestoßen. Schauen wir uns an, warum dies als Anti-Pattern angesehen wird und wie man es richtig umgestaltet.
Das Problem mit der übermäßigen Verwendung von switch-Anweisungen
Eine switch-Anweisung, die das Verhalten basierend auf einem Typ- oder Enumerationswert bestimmt, ist ein klassisches Zeichen dafür, dass Ihr Code möglicherweise gegen das Open/Closed-Prinzip (OCP) verstößt – eines der Kernprinzipien des SOLID-Designs. Das OCP gibt an, dass Ihr Code für Erweiterungen geöffnet, aber für Änderungen geschlossen sein sollte. Eine switch-Anweisung, die mit dem Hinzufügen neuer Fälle wächst, zwingt Sie, vorhandenen Code zu ändern, wodurch er spröde und schwieriger zu verwalten ist.
Es ist jedoch wichtig zu erkennen, dass nicht jede switch-Anweisung durch Polymorphismus ersetzt werden muss. Wenn Ihr Fallsatz endlich, stabil und leicht verständlich ist, ist eine switch-Anweisung möglicherweise die bessere Wahl. Die Praktikabilität sollte immer vor der blinden Anwendung von Designmustern stehen.
Beispiel für eine switch-Anweisung, die möglicherweise umgestaltet werden muss
Betrachten Sie ein grundlegendes Zahlungsabwicklungssystem:
public class PaymentProcessor
{
public void ProcessPayment(PaymentType paymentType)
{
switch (paymentType)
{
case PaymentType.CreditCard:
ProcessCreditCardPayment();
Pause;
case PaymentType.PayPal:
ProcessPayPalPayment();
Pause;
case PaymentType.Bitcoin:
ProcessBitcoinPayment();
Pause;
default:
throw new ArgumentException("Nicht unterstützter Zahlungstyp");
}
}
}
Auf den ersten Blick mag das in Ordnung erscheinen. Jedes Mal, wenn eine neue Zahlungsmethode eingeführt wird, müssen wir diese Erklärung jedoch ändern. Dies ist ein klarer Verstoß gegen OCP, der zu unnötigem Wartungsaufwand und einer erhöhten Wahrscheinlichkeit der Einführung von Fehlern führt.switch
Wann sollte man stattdessen Polymorphismus verwenden?
Anstatt eine switch-Anweisung zu verwenden, sollten wir den Entwurf umgestalten, um Polymorphismus zu nutzen, wenn erwartet wird, dass die Anzahl der Fälle zunimmt. So geht’s:
- Definieren Sie eine abstrakte Basisklasse oder Schnittstelle für die Zahlungsabwicklung.
- Implementieren Sie konkrete Klassen für jede Zahlungsart.
- Verwenden Sie die Abhängigkeitsinjektion oder eine Factory, um die richtige Klasse zu instanziieren.
Überarbeiteter Code mithilfe von Polymorphismus
öffentliche Schnittstelle IPaymentProcessor
{
void ProcessPayment();
}
public class CreditCardPaymentProcessor : IPaymentProcessor
{
public void ProcessPayment()
{
Kreditkartenzahlung
verarbeiten }
}
public class PayPalPaymentProcessor : IPaymentProcessor
{ public void ProcessPayment()
{
PayPal-Zahlung
verarbeiten }
}
public class BitcoinPaymentProcessor : IPaymentProcessor
{
public void ProcessPayment()
{
Bitcoin-Zahlungen abwickeln
}
}
Verwenden von Dependency Injection für eine bessere Leistung
Anstatt über eine Liste von Prozessoren zu iterieren, stellt .NET 8 AddKeyedSingleton
bereit, die O(1)-Suchvorgänge basierend auf dem Zahlungstyp ermöglicht:
Dienstleistungen. AddKeyedSingleton<IPaymentProcessor, CreditCardPaymentProcessor>(PaymentType.CreditCard);
Dienstleistungen. AddKeyedSingleton<IPaymentProcessor, PayPalPaymentProcessor>(PaymentType.PayPal);
Dienstleistungen. AddKeyedSingleton<IPaymentProcessor, BitcoinPaymentProcessor>(PaymentType.Bitcoin);
Lösen Sie das Problem dann dynamisch auf:
public class PaymentService
{
private readonly IServiceProvider _serviceProvider;
public PaymentService(IServiceProvider serviceProvider)
{
_serviceProvider = serviceProvider;
}
public void ProcessPayment(PaymentType paymentType)
{
var processor = _serviceProvider.GetRequiredKeyedService<IPaymentProcessor>(paymentType);
processor?. ProcessPayment() ?? throw new InvalidOperationException("Kein Prozessor gefunden");
}
}
Wann eine switch-Anweisung die bessere Wahl sein könnte
Es ist wichtig, Over-Engineering zu vermeiden. Wenn Ihr Anwendungsfall begrenzt ist und sich wahrscheinlich nicht ändern wird, ist eine switch-Anweisung möglicherweise die beste Option. Die blinde Anwendung von Entwurfsmustern kann zu unnötiger Komplexität führen und das Lesen und Warten von Code erschweren. Eine einfache switch-Anweisung lautet:
- Besser lesbar, wenn die Logik klein und in sich geschlossen ist.
- Schneller durch Compiler-Optimierungen wie Sprungtabellen.
- Einfacher zu warten in Fällen, in denen neue Bedingungen nicht häufig eingeführt werden.
Fazit
Switch-Anweisungen sind nicht von Natur aus schlecht, und Polymorphismus ist kein Allheilmittel. Welche Vorgehensweise am besten geeignet ist, hängt vom Kontext der Anwendung ab. Verwenden Sie Polymorphismus, wenn Skalierbarkeit und Flexibilität erforderlich sind, aber halten Sie die Dinge einfach, wenn Ihr Anwendungsfall stabil und endlich ist.
Weiterer Beitrag zum Thema: Open Closed Prinzip oder was ganz anderes: Kompaktes ESP32-C3 Developer Board mit 0.42″ OLED-Display – Ideal für Embedded-Projekte