Ich war immer der Meinung, dass große switch-Anweisungen ein Symptom für schlechtes OOP-Design sind. In der Vergangenheit habe ich Artikel gelesen, in denen dieses Thema behandelt wird, und sie haben alternative OOP-basierte Ansätze bereitgestellt, die normalerweise auf Polymorphismus basieren, um das richtige Objekt für die Behandlung des Falls zu instanziieren.
Ich bin jetzt in einer Situation, die eine monströse switch-Anweisung hat, die auf einem Datenstrom von einem TCP-Socket basiert, in dem das Protokoll im Grunde aus einem Befehl mit Zeilenumbruch besteht, gefolgt von Datenzeilen, gefolgt von einer Endmarkierung. Der Befehl kann einer von 100 verschiedenen Befehlen sein, daher möchte ich einen Weg finden, diese Monster-Switch-Anweisung auf etwas Verwaltbareres zu reduzieren.
Ich habe ein bisschen gegoogelt, um die Lösungen zu finden, an die ich mich erinnere, aber leider ist Google heutzutage für viele Arten von Abfragen zu einer Einöde irrelevanter Ergebnisse geworden.
Gibt es Muster für diese Art von Problem? Anregungen zu möglichen Implementierungen?
Ein Gedanke, den ich hatte, war die Verwendung einer Wörterbuchsuche, bei der der Befehlstext dem zu instanziierenden Objekttyp zugeordnet wurde. Dies hat den schönen Vorteil, dass lediglich ein neues Objekt erstellt und ein neuer Befehl / Typ für neue Befehle in die Tabelle eingefügt wird.
Dies hat jedoch auch das Problem der Typenexplosion. Ich brauche jetzt 100 neue Klassen und muss einen Weg finden, sie sauber mit dem Datenmodell zu verbinden. Ist die "One True Switch-Anweisung" wirklich der richtige Weg?
Ich würde mich über Ihre Gedanken, Meinungen oder Kommentare freuen.
quelle
Antworten:
Sie können einige Vorteile aus einem Befehlsmuster ziehen .
Bei OOP können Sie möglicherweise mehrere ähnliche Befehle zu einer einzigen Klasse zusammenfassen, wenn die Verhaltensvariationen klein genug sind, um eine vollständige Klassenexplosion zu vermeiden (ja, ich kann die OOP-Gurus bereits darüber schreien hören). Wenn das System jedoch bereits OOP ist und jeder der über 100 Befehle wirklich eindeutig ist, machen Sie sie einfach zu eindeutigen Klassen und nutzen Sie die Vererbung, um die allgemeinen Elemente zu konsolidieren.
Wenn das System nicht OOP ist, würde ich OOP nicht nur dafür hinzufügen ... Sie können das Befehlsmuster einfach mit einer einfachen Wörterbuchsuche und Funktionszeigern oder sogar dynamisch generierten Funktionsaufrufen basierend auf dem Befehlsnamen verwenden, abhängig von die Sprache. Anschließend können Sie logisch verknüpfte Funktionen einfach in Bibliotheken gruppieren, die eine Sammlung ähnlicher Befehle darstellen, um eine überschaubare Trennung zu erreichen. Ich weiß nicht, ob es einen guten Begriff für diese Art der Implementierung gibt ... Ich betrachte ihn immer als "Dispatcher" -Stil, basierend auf dem MVC-Ansatz für den Umgang mit URLs.
quelle
Ich sehe zwei switch-Anweisungen als Symptom für ein Nicht-OO-Design, bei dem der Einschalt-Enum-Typ durch mehrere Typen ersetzt werden kann, die unterschiedliche Implementierungen einer abstrakten Schnittstelle bereitstellen. zum Beispiel die folgenden ...
switch (eFoo) { case Foo.This: eatThis(); break; case Foo.That: eatThat(); break; } switch (eFoo) { case Foo.This: drinkThis(); break; case Foo.That: drinkThat(); break; }
... sollte vielleicht umgeschrieben werden als ...
IAbstract { void eat(); void drink(); } class This : IAbstract { void eat() { ... } void drink() { ... } } class That : IAbstract { void eat() { ... } void drink() { ... } }
Jedoch eine switch - Anweisung ist nicht imo so ein starker Indikator dafür , dass die Switch - Anweisung sollte mit etwas anderes ersetzt werden.
quelle
Wenn Sie eine von 100 verschiedenen Aufgaben ausführen müssen, können Sie eine 100-Wege-Verzweigung nicht vermeiden. Sie können es im Kontrollfluss (switch, if-elseif ^ 100) oder in Daten (eine 100-Elemente-Zuordnung von Zeichenfolge zu Befehl / Factory / Strategie) codieren. Aber es wird da sein.
Sie können versuchen, das Ergebnis des 100-Wege-Zweigs von Dingen zu isolieren, die dieses Ergebnis nicht kennen müssen. Vielleicht sind nur 100 verschiedene Methoden in Ordnung; Sie müssen keine Objekte erfinden, die Sie nicht benötigen, wenn der Code dadurch unhandlich wird.
quelle
map_lookup
Funktion ausgelagert, die sie aufruft (die verzweigt). Ich bin mir nicht sicher, was diese Beobachtung für Sie bedeutet. "Es ist nicht wirklich ein Zweig, wenn es woanders passiert"? Die Zuordnung zwischen Eingabe und Ausgabe erfolgt zur Kompilierungszeit für Switch-Anweisungen (und andere Steuerungsflussanweisungen) und zur Laufzeit für Datenstrukturen (höchstwahrscheinlich). Es ist nicht so, dass sich die Form des Schalters zur Laufzeit ändert. Beide Arten der Suche erfordern (und eine ist) einen Zweig. Ich hoffe das hilft :-)Ich denke, dies ist einer der wenigen Fälle, in denen große Schalter die beste Antwort sind, es sei denn, es bietet sich eine andere Lösung an.
quelle
Ich sehe das Strategiemuster. Wenn ich 100 verschiedene Strategien habe ... so sei es. Die riesige switch-Anweisung ist hässlich. Sind alle Befehle gültige Klassennamen? Verwenden Sie in diesem Fall einfach die Befehlsnamen als Klassennamen und erstellen Sie das Strategieobjekt mit Activator.CreateInstance.
quelle
Wenn Sie über eine große switch-Anweisung sprechen, fallen Ihnen zwei Dinge ein:
Andererseits kann eine Kartenimplementierung OCP entsprechen und möglicherweise mit O (1) arbeiten.
quelle
O(1)
. Siehe diese Antwort: stackoverflow.com/a/1055261/4834Ich würde sagen, dass das Problem nicht die große switch-Anweisung ist, sondern die Verbreitung des darin enthaltenen Codes und der Missbrauch von Variablen mit falschem Gültigkeitsbereich.
Ich habe dies in einem Projekt selbst erlebt, als immer mehr Code in den Switch einging, bis er nicht mehr wartbar war. Meine Lösung bestand darin, eine Parameterklasse zu definieren, die den Kontext für die Befehle enthielt (Name, Parameter, was auch immer, vor dem Wechsel gesammelt), eine Methode für jede case-Anweisung zu erstellen und diese Methode mit dem Parameterobjekt aus dem case aufzurufen.
Natürlich ist ein vollständig OOP-Befehls-Dispatcher (basierend auf Magie wie Reflexion oder Mechanismen wie Java-Aktivierung) schöner, aber manchmal möchten Sie einfach nur Dinge reparieren und die Arbeit erledigen;)
quelle
Sie können ein Wörterbuch (oder eine Hash-Map, wenn Sie in Java codieren) verwenden (es wird von Steve McConnell als tabellengesteuerte Entwicklung bezeichnet).
quelle
Eine Möglichkeit, wie Sie sich verbessern könnten, würde dazu führen, dass Ihr Code von den Daten gesteuert wird. So stimmen Sie beispielsweise für jeden Code mit etwas überein, das damit umgeht (Funktion, Objekt). Sie können Reflection auch verwenden, um Zeichenfolgen abzubilden, die die Objekte / Funktionen darstellen, und sie zur Laufzeit aufzulösen. Möglicherweise möchten Sie jedoch einige Experimente durchführen, um die Leistung zu bewerten.
quelle
Der beste Weg, um dieses spezielle Problem zu lösen: Serialisierung und Protokolle sauber, besteht darin, eine IDL zu verwenden und den Marshalling-Code mit switch-Anweisungen zu generieren. Da alle Muster (Prototype Factory, Befehlsmuster usw.), die Sie anderweitig verwenden möchten, müssen Sie eine Zuordnung zwischen einer Befehls-ID / Zeichenfolge und einem Klassen- / Funktionszeiger initialisieren, die seitdem langsamer als switch-Anweisungen ausgeführt wird Der Compiler kann die perfekte Hash-Suche für switch-Anweisungen verwenden.
quelle
Ja, ich denke, große case-Anweisungen sind ein Symptom dafür, dass der eigene Code verbessert werden kann ... normalerweise durch Implementierung eines objektorientierteren Ansatzes. Wenn ich zum Beispiel die Art der Klassen in einer switch-Anweisung bewerte, bedeutet dies fast immer, dass ich wahrscheinlich Generics verwenden könnte, um die switch-Anweisung zu entfernen.
quelle
Sie können hier auch einen Sprachansatz wählen und die Befehle mit den zugehörigen Daten in einer Grammatik definieren. Sie können dann ein Generator-Tool verwenden, um die Sprache zu analysieren. Ich habe Ironie für diesen Zweck verwendet. Alternativ können Sie das Interpreter-Muster verwenden.
Meiner Meinung nach besteht das Ziel nicht darin, das reinste OO-Modell zu erstellen, sondern ein flexibles, erweiterbares, wartbares und leistungsstarkes System zu schaffen.
quelle
Ich habe kürzlich ein ähnliches Problem mit einer riesigen switch-Anweisung und habe den hässlichen Schalter durch die einfachste Lösung, eine Nachschlagetabelle und eine Funktion oder Methode, die den erwarteten Wert zurückgibt, beseitigt. Das Befehlsmuster ist eine gute Lösung, aber 100 Klassen zu haben, finde ich nicht gut. Also hatte ich so etwas wie:
switch(id) case 1: DoSomething(url_1) break; case 2: DoSomething(url_2) break; .. .. case 100 DoSomething(url_100) break;
und ich habe mich geändert für:
string url = GetUrl(id); DoSomthing(url);
GetUrl kann in die Datenbank wechseln und die gesuchte URL zurückgeben, oder es kann sich um ein Wörterbuch im Speicher handeln, das die 100 URLs enthält. Ich hoffe, dies könnte jedem da draußen helfen, wenn er eine riesige monströse switch-Anweisung ersetzt.
quelle
Denken Sie daran, wie Windows ursprünglich in der Anwendungsnachrichtenpumpe geschrieben wurde. Es saugte. Anwendungen werden mit den mehr hinzugefügten Menüoptionen langsamer ausgeführt. Da der gesuchte Befehl immer weiter unten in der switch-Anweisung endete, wurde das Warten auf die Antwort immer länger. Es ist nicht akzeptabel, lange switch-Anweisungen zu haben. Ich habe einen AIX-Daemon als POS-Befehlshandler erstellt, der 256 eindeutige Befehle verarbeiten kann, ohne zu wissen, was in dem über TCP / IP empfangenen Anforderungsdatenstrom enthalten ist. Das allererste Zeichen des Streams war ein Index in ein Funktionsarray. Jeder nicht verwendete Index wurde auf einen Standardnachrichtenhandler festgelegt. loggen Sie sich ein und verabschieden Sie sich.
quelle