it-swarm.com.de

Große Switch-Anweisungen: Bad OOP?

Ich war immer der Meinung, dass große switch-Anweisungen ein Symptom für bad OOP Design sind. In der Vergangenheit habe ich Artikel gelesen, die dieses Thema behandeln, und sie haben altnerative OOP -basierte Ansätze bereitgestellt, die normalerweise auf Polymorphismus basieren, um das richtige Objekt für die Bearbeitung des Falls zu instanziieren.

Ich befinde mich jetzt in einer Situation, in der es eine monsteröse switch-Anweisung gibt, die auf einem Datenstrom aus einem TCP Socket basiert, in dem das Protokoll im Wesentlichen aus einem Zeilenumbruchbefehl besteht, gefolgt von Datenzeilen gefolgt von einem Ende Marker. Der Befehl kann einer von 100 verschiedenen Befehlen sein. Daher möchte ich einen Weg finden, um diese Monsterschalteranweisung auf etwas überschaubarer zu reduzieren.

Ich habe ein bisschen gegoogelt, um die Lösungen zu finden, an die ich mich erinnere, aber leider ist Google heutzutage zu einer Einöde irrelevanter Ergebnisse für viele Arten von Anfragen geworden.

Gibt es Muster für diese Art von Problem? Anregungen zu möglichen Implementierungen? 

Ein Gedanke, den ich hatte, war, eine Wörterbuchsuche zu verwenden, die den Befehlstext an den Objekttyp anpasst, um zu instanziieren. Dies hat den Vorteil von Nice, nur ein neues Objekt zu erstellen und einen neuen Befehl/Typ in die Tabelle für alle neuen Befehle einzufügen. 

Dies hat jedoch auch das Problem der Typexplosion. Ich brauche jetzt 100 neue Klassen, und ich 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.

71

Möglicherweise profitieren Sie von einem Command Pattern

Bei OOP können Sie möglicherweise mehrere ähnliche Befehle in eine einzige Klasse zusammenfassen, wenn die Verhaltensschwankungen klein genug sind, um eine vollständige Klassenexplosion zu vermeiden (ja, ich kann die Gurus OOP darüber schon kreischen hören) . Wenn das System jedoch bereits OOP ist und jeder der 100+ Befehle wirklich einzigartig ist, dann legen Sie einfach eindeutige Klassen fest und nutzen Sie die Vererbung, um das allgemeine Wissen zu konsolidieren.

Wenn das System nicht OOP ist, würde ich nicht einfach OOP hinzufügen ... Sie können das Befehlsmuster ganz einfach mit einem einfachen Wörterbuchsuch- und Funktionszeiger oder sogar dynamisch generierten Funktionsaufrufen verwenden, die auf dem Befehl basieren Name, abhängig von der Sprache. Sie können dann nur logisch verknüpfte Funktionen 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 denke immer an einen "Dispatcher" -Stil, der auf dem MVC-Ansatz zur Handhabung von URLs basiert.

33
nezroy

Ich sehe two switch-Anweisungen als ein Symptom des Nicht-OO-Designs, wobei der Einschalt-Enummentyp durch mehrere Typen ersetzt werden kann, die verschiedene Implementierungen einer abstrakten Schnittstelle bereitstellen. zum Beispiel das folgende ...

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() { ... }
}

One switch-Anweisung ist nicht ein so starker Indikator, dass die switch-Anweisung durch etwas anderes ersetzt werden sollte.

24
ChrisW

Der Befehl kann einer von 100 verschiedenen Befehlen sein

Wenn Sie eines von 100 verschiedenen Dingen ausführen müssen, können Sie nicht vermeiden, einen 100-Wege-Zweig zu haben. Sie können es im Kontrollfluss (switch, if-elseif ^ 100) oder in Daten (eine 100-Element-Zuordnung von String 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. Es ist nicht notwendig, Objekte zu erfinden, die Sie nicht benötigen, wenn der Code dadurch unhandlich wird.

16
Jonas Kölker

Ich denke, dies ist einer der wenigen Fälle, in denen große Schalter die beste Antwort sind, wenn sich keine andere Lösung ergibt.

3
Loren Pechtel

Wenn Sie über eine große switch-Anweisung sprechen, fallen Ihnen zwei Dinge ein:

  1. Es verstößt gegen OCP - Sie könnten ständig eine große Funktion beibehalten. 
  2. Sie könnten eine schlechte Leistung haben: O (n).

Andererseits kann eine Kartenimplementierung OCP-konform sein und möglicherweise mit O (1) funktionieren.

2
quamrana

Ich sehe das Strategiemuster. Wenn ich 100 verschiedene Strategien habe, dann sei es so. Die gigantische Wechselaussage ist hässlich. Sind alle Befehle gültige Klassennamen? Wenn ja, verwenden Sie einfach die Befehlsnamen als Klassennamen und erstellen Sie das Strategieobjekt mit Activator.CreateInstance.

2
Jason Punyon

Sie können ein Wörterbuch verwenden (oder eine Hash-Map, wenn Sie in Java programmieren) (es wird als tabellengesteuerte Entwicklung von Steve McConnell bezeichnet).

1
Nicolas Dorier

Ich würde sagen, dass das Problem nicht die große switch-Anweisung ist, sondern eher die Verbreitung von darin enthaltenem Code und der Missbrauch von Variablen mit falschem Umfang.

Ich habe das in einem Projekt selbst erlebt, als immer mehr Code in den Switch ging, bis er unmerklich wurde. Meine Lösung bestand darin, eine Parameterklasse zu definieren, die den Kontext für die Befehle (Name, Parameter, was auch immer vor dem Switch erfasst) enthielt, eine Methode für jede case-Anweisung zu erstellen und diese Methode mit dem Parameterobjekt aus dem Fall aufzurufen.

Natürlich ist ein vollständig ausgeführter OOP - Dispatcher (basierend auf Magie wie Reflektion oder Mechanismen wie Java-Aktivierung) schöner, aber manchmal möchten Sie nur Dinge reparieren und die Arbeit erledigen;)

1
devio

Ja, ich denke, große Case-Anweisungen sind ein Symptom dafür, dass man den Code verbessern kann ... in der Regel durch einen objektorientierteren Ansatz. Wenn ich beispielsweise den Typ der Klassen in einer switch-Anweisung auswerte, bedeutet das fast immer, dass ich wahrscheinlich Generics verwenden könnte, um die switch-Anweisung zu entfernen.

0
cyclo

Eine Art, wie ich sehe, dass Sie verbessern könnten, würde Ihren Code von den Daten steuern lassen, so dass Sie beispielsweise für jeden Code etwas finden, das ihn behandelt (Funktion, Objekt). Sie können Reflection auch verwenden, um Strings abzubilden, die die Objekte/Funktionen darstellen, und diese zur Laufzeit aufzulösen. Sie können jedoch auch einige Experimente zur Leistungsbewertung durchführen.

0
Otávio Décio

Stellen Sie sich vor, wie Windows ursprünglich in die Anwendungsnachrichtenpumpe geschrieben wurde. Es hat gesaugt. Anwendungen würden mit den hinzugefügten Menüoptionen langsamer laufen. Da der gesuchte Befehl immer weiter unten in der switch-Anweisung endete, wurde die Antwort immer länger gewartet. Es ist nicht akzeptabel, lange switch-Anweisungen zu haben, Punkt. Ich habe einen AIX-Dämon als POS-Befehlshandler erstellt, der 256 eindeutige Befehle verarbeiten kann, ohne zu wissen, was sich in dem über TCP/IP empfangenen Anforderungsstrom befindet. Das allererste Zeichen des Streams war ein Index in ein Funktionsarray. Jeder nicht verwendete Index wurde auf einen Standard-Meldungshandler festgelegt. Loggen Sie sich ein und verabschieden Sie sich. 

0
Robert Achmann

Sie können hier auch einen Sprachansatz verwenden und die Befehle mit zugehörigen Daten in einer Grammatik definieren. Sie können dann ein Generatorwerkzeug verwenden, um die Sprache zu analysieren. Ich habe Ironie für diesen Zweck verwendet. Alternativ können Sie das Interpreter-Muster verwenden.

Meines Erachtens besteht das Ziel nicht darin, das reinste OO - Modell zu bauen, sondern ein flexibles, erweiterbares, wartbares und leistungsstarkes System. 

0
Rine

Ich habe kürzlich ein ähnliches Problem mit einer großen switch-Anweisung und bin mit dem hässlichsten Schalter durch die meisten simple solution a Lookup-Tabellen und eine Funktion oder Methode, die den erwarteten Wert zurückgibt, losgeworden. Das Befehlsmuster ist eine schöne Lösung, aber 100 Klassen zu haben ist nicht schön, denke ich.

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);

die GetUrl kann zur Datenbank gehen und die URL zurückgeben, nach der Sie suchen, oder es könnte sich um ein Wörterbuch im Speicher handeln, das die 100 URLs enthält .. __ Ich hoffe, dass dies jedem helfen kann, wenn er riesige monströse Switch-Anweisungen ersetzt.

0
darmis

Der beste Weg, um dieses spezielle Problem zu bewältigen: Serialisierung und Protokolle werden sauber verwendet, indem eine IDL verwendet und der Marshalling-Code mit switch-Anweisungen generiert wird. Da die von Ihnen verwendeten Muster (Prototyp-Factory, Befehlsmuster usw.) ansonsten anders verwendet werden sollen, müssen Sie ein Mapping zwischen einer Befehls-ID/Zeichenfolge und einem Klassen-/Funktionszeiger initialisieren. In diesem Fall läuft es langsamer als switch-Anweisungen Compiler kann eine perfekte Hash-Suche für switch-Anweisungen verwenden.

0
ididak