Switch-Anweisungen umgestalten und gibt es überhaupt eine echte Verwendung für Switch-Anweisungen?

28

Ich habe diesen Artikel gelesen und mich gefragt, ob wir alle switch-Anweisungen entfernen, indem wir sie durch ein Dictionary oder eine Factory ersetzen, sodass in meinen Projekten überhaupt keine switch-Anweisungen vorhanden sind.

Etwas stimmte nicht ganz.

Die Frage ist, ob switch-Anweisungen wirklich verwendet werden können, oder ob wir sie durch ein Wörterbuch oder eine Factory-Methode ersetzen (bei Verwendung einer Factory-Methode müssen natürlich mindestens switch-Anweisungen zum Erstellen der Objekte verwendet werden mit der fabrik ... aber das ist schon alles).

Kanini
quelle
10
Angenommen, Sie implementieren eine Factory. Wie entscheiden Sie, welcher Objekttyp erstellt werden soll?
CodeART
@CodeWorks: Ich werde natürlich irgendwo Bedingungen haben, um zu entscheiden, welche konkrete Implementierung verwendet werden soll.
Kanini
@CodeWorks: Natürlich mit einem virtuellen Konstruktor. (Und wenn Sie ein Factory-Pattern nicht auf diese Weise implementieren können, benötigen Sie eine bessere Sprache.)
Mason Wheeler

Antworten:

44

Beide switchAussagen und Polymorphismen haben ihre Verwendung. Beachten Sie jedoch, dass es auch eine dritte Option gibt (in Sprachen, die Funktionszeiger / Lambda und Funktionen höherer Ordnung unterstützen): Zuordnen der fraglichen Bezeichner zu Handlerfunktionen. Dies ist z. B. in C, das keine OO-Sprache ist, und in C #, das * ist, aber (noch) nicht in Java, das auch OO * ist, verfügbar.

In einigen prozeduralen Sprachen (ohne Polymorphismus oder Funktionen höherer Ordnung) waren switch/ if-elseAnweisungen die einzige Möglichkeit, eine Klasse von Problemen zu lösen. So viele Entwickler, die sich an diese Denkweise gewöhnt hatten, verwendeten sie switchauch in OO-Sprachen, in denen Polymorphismus oft die bessere Lösung ist. Aus diesem Grund wird häufig empfohlen, switchAussagen zugunsten des Polymorphismus zu vermeiden / umzugestalten .

In jedem Fall ist die beste Lösung immer fallabhängig. Die Frage ist: Mit welcher Option erhalten Sie auf lange Sicht saubereren, präziseren und wartbareren Code?

Switch-Anweisungen können oft unhandlich werden, da sie Dutzende von Fällen aufweisen und ihre Wartung schwierig machen. Da Sie sie in einer einzigen Funktion behalten müssen, kann diese Funktion sehr groß werden. In diesem Fall sollten Sie eine Umgestaltung in Richtung einer kartenbasierten und / oder polymorphen Lösung in Betracht ziehen.

Wenn dasselbe switchan mehreren Stellen auftaucht, ist Polymorphismus wahrscheinlich die beste Option, um all diese Fälle zu vereinheitlichen und den Code zu vereinfachen. Vor allem, wenn in Zukunft weitere Fälle erwartet werden; Je mehr Stellen Sie jedes Mal aktualisieren müssen, desto mehr Fehlermöglichkeiten gibt es. Oft sind die einzelnen Fallbearbeiter jedoch so einfach, oder es gibt so viele von ihnen, oder sie sind so miteinander verbunden, dass ihre Umgestaltung in eine vollständige polymorphe Klassenhierarchie zu viel Aufwand bedeutet oder zu viel dupliziertem Code und / oder Wirrwarr führt. schwer zu Klassenhierarchie beizubehalten. In diesem Fall ist es möglicherweise einfacher, stattdessen Funktionen / Lambdas zu verwenden (sofern Ihre Sprache dies zulässt).

Wenn Sie jedoch eine switchan einem einzigen Ort haben und nur wenige Fälle etwas Einfaches tun, ist es möglicherweise die beste Lösung, sie so zu belassen, wie sie ist.

* Ich benutze den Begriff "OO" hier locker; Ich interessiere mich nicht für konzeptuelle Debatten darüber, was "echt" oder "rein" ist.

Péter Török
quelle
4
+1. Außerdem neigen diese Empfehlungen dazu zu formulieren, " Polymorphismus dem Wechsel vorzuziehen ", und dies ist eine gute Wortwahl, sehr im Einklang mit dieser Antwort. Es wird anerkannt, dass es Umstände gibt, unter denen ein verantwortlicher Codierer die andere Wahl treffen könnte.
Carl Manaster
1
Und manchmal möchten Sie den Switch-Ansatz, auch wenn sich eine Unmenge davon in Ihrem Code befindet. Ich habe ein Stück Code im Sinn, das ein 3D-Labyrinth erzeugt. Die Speichernutzung würde erheblich zunehmen, wenn die Ein-Byte-Zellen im Array durch Klassen ersetzt würden.
Loren Pechtel
14

Hier bin ich wieder ein Dinosaurier ...

Switch-Anweisungen sind an sich nicht schlecht, es geht um die Verwendung der Anweisungen.

Die offensichtlichste ist die "gleiche" switch-Anweisung, die in Ihrem Code immer wieder wiederholt wird und die fehlerhaft ist (wenn Sie dort waren und dies getan haben, würden Sie sich alle Mühe geben, es nicht noch einmal zu tun) mit der Verwendung von Polymorphismus. Normalerweise haben verschachtelte Fälle auch etwas ziemlich Schreckliches (ich hatte früher ein absolutes Monster - ich bin mir nicht ganz sicher, wie ich jetzt damit umgehe, außer "besser").

Wörterbuch als Schalter Ich finde es herausfordernder - grundsätzlich ja, wenn Ihr Schalter 100% der Fälle abdeckt, aber wenn Sie Standardfälle oder Fälle ohne Aktion haben möchten, wird es ein bisschen interessanter.

Ich denke, es geht darum, Wiederholungen zu vermeiden und sicherzustellen, dass wir unsere Objektgraphen an den richtigen Stellen zusammenstellen.

Aber es gibt auch das Argument des Verständnisses (Wartbarkeit), und dieses schneidet in beide Richtungen - sobald Sie verstehen, wie alles funktioniert (das Muster und die App, in der es implementiert ist), ist es einfach ... aber wenn Sie zu einer einzelnen Codezeile kommen, wo Sie sind Um etwas Neues hinzuzufügen, muss man dann über den ganzen Platz springen, um herauszufinden, was man hinzufügen / ändern muss.

Trotz der hohen Leistungsfähigkeit unserer Entwicklungsumgebungen halte ich es immer noch für wünschenswert, auf Papier gedruckten Code verstehen zu können - können Sie den Code mit Ihrem Finger verfolgen? Ich akzeptiere das eigentlich nicht, mit vielen guten Praktiken kann man heute nicht und das aus guten Gründen, aber das bedeutet, dass es schwieriger ist, mit Code auf den neuesten Stand zu kommen (oder vielleicht bin ich einfach nur alt ...)

Murph
quelle
4
Ich hatte einen Lehrer, der sagte, dass idealerweise Code geschrieben werden sollte, "damit jemand, der nichts über Programmierung wusste (wie vielleicht Ihre Mutter), es verstehen kann." Leider ist das ein Ideal, aber vielleicht sollten wir unsere Mütter trotzdem in Code Reviews einbeziehen!
Michael K
+1 für "aber wenn Sie zu einer einzelnen Codezeile kommen, in der Sie etwas Neues hinzufügen müssen, müssen Sie überall hin springen, um herauszufinden, was Sie hinzufügen / ändern müssen"
schnell_now
8

Switch-Statements vs. Subtyp-Polymorphismus sind ein altes Problem und werden in der FP-Community häufig in Diskussionen zum Ausdrucksproblem angesprochen .

Grundsätzlich haben wir Typen (Klassen) und Funktionen (Methoden). Wie codieren wir Dinge, damit es einfach ist, neue Typen oder neue Methoden hinzuzufügen?

Wenn Sie im OO-Stil programmieren, ist es schwierig, eine neue Methode hinzuzufügen (da dies eine Umgestaltung aller vorhandenen Klassen bedeuten würde), aber es ist sehr einfach, neue Klassen hinzuzufügen, die dieselben Methoden wie zuvor verwenden.

Wenn Sie dagegen eine switch-Anweisung (oder deren OO-Äquivalent, das Observer-Muster) verwenden, ist das Hinzufügen neuer Funktionen sehr einfach, das Hinzufügen neuer Fälle / Klassen jedoch schwierig.

Es ist nicht einfach, eine gute Erweiterbarkeit in beide Richtungen zu haben. Wenn Sie also Ihren Code schreiben, müssen Sie entscheiden, ob Sie Polymorphismus oder switch-Anweisungen verwenden, je nachdem, in welche Richtung Sie letztere mit größerer Wahrscheinlichkeit erweitern.

hugomg
quelle
4
Beseitigen wir alle switch-Anweisungen, indem wir sie durch ein Dictionary oder eine Factory ersetzen, sodass in meinen Projekten überhaupt keine switch-Anweisungen vorhanden sind.

Nein. Solche Absoluten sind selten eine gute Idee.

An vielen Orten bietet ein Dictionary / Lookup / Factory / Polymorphic-Versand ein besseres Design als eine switch-Anweisung, aber Sie müssen das Dictionary noch füllen. In bestimmten Fällen kann dies zu einer Verschleierung der tatsächlichen Abläufe führen und das einfache In-Line-Setzen der switch-Anweisung ist lesbarer und wartbarer.

Telastyn
quelle
Und wirklich, wenn Sie die Factory, das Wörterbuch und die Suche aufgerollt hätten, wäre dies im Grunde genommen eine switch-Anweisung: Wenn array [hash (search)], dann rufen Sie array [hash (search)] auf. Es wäre zwar laufzeitverlängerbar, welche kompilierten Switches es nicht sind.
Zan Lynx
@ZanLynx, zumindest mit C # ist das Gegenteil der Fall. Wenn Sie sich die IL ansehen, die für eine nicht triviale switch-Anweisung erstellt wurde, sehen Sie, dass der Compiler sie in ein Dictionary verwandelt. Sie ist schneller.
David Arno
@DavidArno: "das genaue Gegenteil"? So wie ich es gelesen habe, haben wir genau dasselbe gesagt. Wie kann es umgekehrt sein?
Zan Lynx,
2

Da dies sprachunabhängig ist, funktioniert Fall-Through-Code am besten mit switchAnweisungen:

switch(something) {
   case 1:
      foo();
   case 2:
      bar();
      baz();
      break;

   case 3:
      bang();
   default:
      bizzap();
      break;
}

Entspricht if, mit einem sehr umständlichen Standardfall. Und denken Sie daran, dass die Bedingungsliste umso länger wird, je mehr Fehler auftreten:

if (1 == something) {
   foo();
}
if (1 == something || 2 == something) {
   bar();
   baz();
}
if (3 == something) {
   bang();
}
if (1 != something && 2 != something) {
   bizzap();
}

(Angesichts der Aussagen einiger anderer Antworten habe ich jedoch das Gefühl, den Punkt der Frage verpasst zu haben ...)

Izkata
quelle
Ich würde die Verwendung von Fall-through-Anweisungen in a switchals auf der gleichen Ebene wie in a betrachten goto. Wenn der auszuführende Algorithmus Kontrollflüsse erfordert, die zu strukturierten Programmierkonstrukten passen, sollte man solche Konstrukte verwenden, aber wenn der Algorithmus zu solchen Konstrukten nicht passt, ist die Verwendung gotomöglicherweise besser, als zu versuchen, Flags oder andere Kontrolllogiken hinzuzufügen, um zu anderen Kontrollstrukturen zu passen .
Supercat
1

Switch-Anweisungen als Fallunterscheidung haben eine reale Verwendung. Funktionale Programmiersprachen verwenden so genannte Mustervergleiche, mit denen Sie Funktionen je nach Eingabe unterschiedlich definieren können. In objektorientierten Sprachen kann dasselbe Ziel jedoch eleganter durch Verwendung von Polymorphismus erreicht werden. Sie rufen einfach die Methode auf und abhängig vom tatsächlichen Typ des Objekts wird die entsprechende Implementierung der Methode ausgeführt. Dies ist der Grund für die Idee, dass switch-Anweisungen ein Codegeruch sind. Selbst in OO-Sprachen können Sie sie jedoch nützlich finden, um das abstrakte Factory-Muster zu implementieren.

tl; dr - sie sind nützlich, aber oft kann man es besser machen.

Narbenkühlschrank
quelle
2
Da rieche ich ein bisschen Voreingenommenheit - warum ist Polymorphismus eleganter als Mustervergleich? Und warum glaubst du, dass es in FP keinen Polymorphismus gibt?
tdammers
@tdammers Zunächst wollte ich nicht implizieren, dass es in FP keinen Polymorphismus gibt. Haskell unterstützt Ad-hoc- und parametrischen Polymorphismus. Der Mustervergleich ist für einen algebraischen Datentyp sinnvoll. Für externe Module ist es jedoch erforderlich, Konstruktoren verfügbar zu machen. Dies unterbricht eindeutig die Kapselung eines abstrakten Datentyps (der mit algebraischen Datentypen realisiert wurde). Deshalb finde ich, dass Polymorphismus eleganter ist (und in FP möglich ist).
scarfridge
Sie vergessen den Polymorphismus durch Typklassen und Instanzen.
tdammers
Haben Sie jemals von dem Ausdrucksproblem gehört ? Der OO und der FP sind in verschiedenen Situationen besser, wenn Sie aufhören, darüber nachzudenken.
hugomg
@tdammers Von welcher Art von Polymorphismus habe ich gesprochen, als ich Ad-hoc-Polymorphismus erwähnte? Ad-hoc-Polymorphismus wird in Haskell durch Typklassen realisiert. Wie kannst du sagen, dass ich es vergessen habe? Einfach nicht wahr.
scarfridge