Betrachten Sie das folgende Beispiel. Jede Änderung an der ColorChoice-Enumeration wirkt sich auf alle IWindowColor-Unterklassen aus.
Neigen Aufzählungen dazu, spröde Schnittstellen zu verursachen? Gibt es etwas Besseres als eine Aufzählung, um mehr polymorphe Flexibilität zu ermöglichen?
enum class ColorChoice
{
Blue = 0,
Red = 1
};
class IWindowColor
{
public:
ColorChoice getColor() const=0;
void setColor( const ColorChoice value )=0;
};
Bearbeiten: Entschuldigung für die Verwendung von Farbe als mein Beispiel, das ist nicht, was die Frage ist. Hier ist ein anderes Beispiel, das den roten Hering vermeidet und mehr Informationen darüber liefert, was ich unter Flexibilität verstehe.
enum class CharacterType
{
orc = 0,
elf = 1
};
class ISomethingThatNeedsToKnowWhatTypeOfCharacter
{
public:
CharacterType getCharacterType() const;
void setCharacterType( const CharacterType value );
};
Stellen Sie sich außerdem vor, dass Handles für die entsprechende Unterklasse ISomethingThatNeedsToKnowWhatTypeOfCharacter durch ein werkseitiges Entwurfsmuster ausgegeben werden. Jetzt habe ich eine API, die in Zukunft nicht für eine andere Anwendung erweitert werden kann, bei der die zulässigen Zeichentypen {Mensch, Zwerg} sind.
Bearbeiten: Nur um konkreter zu sein, woran ich arbeite. Ich entwerfe eine starke Bindung dieser ( MusicXML- ) Spezifikation und verwende Enum-Klassen, um die Typen in der Spezifikation darzustellen, die mit xs: enumeration deklariert sind. Ich versuche darüber nachzudenken, was passiert, wenn die nächste Version (4.0) herauskommt. Könnte meine Klassenbibliothek in einem 3.0-Modus und in einem 4.0-Modus funktionieren? Wenn die nächste Version zu 100% abwärtskompatibel ist, dann vielleicht. Aber wenn Aufzählungswerte aus der Spezifikation entfernt wurden, bin ich tot im Wasser.
quelle
Antworten:
Bei richtiger Verwendung sind Aufzählungen weitaus lesbarer und robuster als die "magischen Zahlen", die sie ersetzen. Ich sehe normalerweise nicht, dass sie Code spröder machen. Zum Beispiel:
value
ein gültiger Farbwert vorliegt oder nicht. Der Compiler hat das schon gemacht.enum class
Funktion in modernem C ++ können Sie sogar Leute zwingen, immer das erstere anstelle des letzteren zu schreiben.Die Verwendung einer Aufzählung für Farben ist jedoch fraglich, da es in vielen (den meisten?) Situationen keinen Grund gibt, den Benutzer auf einen so kleinen Satz von Farben zu beschränken. Sie können sie auch in beliebigen RGB-Werten übergeben. Bei den Projekten, mit denen ich arbeite, taucht eine kleine Liste solcher Farben immer nur als Teil einer Reihe von "Themen" oder "Stilen" auf, die als dünne Abstraktion über konkrete Farben fungieren sollen.
Ich bin mir nicht sicher, worauf sich Ihre Frage nach der "polymorphen Flexibilität" bezieht. Enums haben keinen ausführbaren Code, daher gibt es nichts, was man polymorph machen könnte. Vielleicht suchen Sie nach dem Befehlsmuster ?
Bearbeiten: Nach der Bearbeitung ist mir immer noch nicht klar, nach welcher Art von Erweiterbarkeit Sie suchen, aber ich denke immer noch, dass das Befehlsmuster dem "polymorphen Enum" am nächsten kommt.
quelle
Nein, tut es nicht. Es gibt zwei Fälle: Implementierer werden entweder
Speichern, Zurückgeben und Weiterleiten von Enum-Werten, die niemals bearbeitet werden. In diesem Fall bleiben sie von Änderungen der Enum-Werte unberührt
arbeite mit individuellen enum - Werten, wobei jede Änderung in der ENUM muß natürlich natürlich unausweichlich, notwendigerweise , für mit einer entsprechenden Änderung in der Logik der Implementierer berücksichtigt werden.
Wenn Sie "Sphere", "Rectangle" und "Pyramid" in eine "Shape" -Aufzählung einfügen und eine solche Aufzählung an eine
drawSolid()
Funktion übergeben, die Sie zum Zeichnen des entsprechenden Volumenkörpers geschrieben haben, und dann eines Morgens entscheiden, eine " "Ikositetrahedron", können Sie nicht erwarten, dass diedrawSolid()
Funktion davon unberührt bleibt. Wenn Sie damit gerechnet haben, dass es irgendwie icositetrahedrons zeichnet, ohne dass Sie zuerst den eigentlichen Code schreiben müssen, um icositetrahedrons zu zeichnen, ist dies Ihre Schuld, nicht die Schuld der Aufzählung. So:Nein, das tun sie nicht. Was spröde Schnittstellen verursacht, ist, dass Programmierer sich als Ninjas betrachten und versuchen, ihren Code zu kompilieren, ohne dass ausreichende Warnungen aktiviert sind. Der Compiler warnt sie dann nicht, dass ihre
drawSolid()
Funktion eineswitch
Anweisung enthält , in der einecase
Klausel für den neu hinzugefügten Enum-Wert "Ikositetrahedron" fehlt .Die Art und Weise, wie es funktionieren soll, ist analog zum Hinzufügen einer neuen rein virtuellen Methode zu einer Basisklasse: Sie müssen diese Methode dann auf jedem einzelnen Erben implementieren, oder das Projekt wird und sollte nicht erstellt.
Um ehrlich zu sein, sind Aufzählungen kein objektorientiertes Konstrukt. Sie sind eher ein pragmatischer Kompromiss zwischen dem objektorientierten Paradigma und dem strukturierten Programmierparadigma.
Die reine objektorientierte Art, Dinge zu tun, besteht darin, überhaupt keine Aufzählungen zu haben und stattdessen Objekte zu haben.
Die rein objektorientierte Art, das Beispiel mit den Festkörpern zu implementieren, ist natürlich die Verwendung des Polymorphismus: Statt einer einzigen monströsen zentralisierten Methode, die alles zu zeichnen weiß und zu sagen hat, welcher Festkörper zu zeichnen ist, deklarieren Sie ein " Solide "Klasse mit einer abstrakten (rein virtuellen)
draw()
Methode, und dann fügen Sie die Unterklassen" Sphere "," Rectangle "und" Pyramid "hinzu, von denen jede eine eigene Implementierung hat,draw()
die weiß, wie sie sich selbst zeichnet.Auf diese Weise müssen Sie, wenn Sie die Unterklasse "Ikositetrahedron" einführen, nur eine
draw()
Funktion dafür bereitstellen , und der Compiler wird Sie daran erinnern, dies zu tun, indem Sie sonst nicht "Icositetrahedron" instanziieren lassen.quelle
default:
Klausel dies tun sollte. Eine schnelle Suche nach dem Thema ergab keine genaueren Ergebnisse, so dass dies als Thema einer anderenprogrammers SE
Frage geeignet sein könnte .Pyramid
eigentlich vondraw()
einer Pyramide kennt . Bestenfalls könnte es vonSolid
einerGetTriangles()
Methode stammen und sie haben , und Sie könnten sie an einenSolidDrawer
Dienst übergeben. Ich dachte, wir würden uns von den Beispielen für physische Objekte als Beispiele für Objekte in OOP entfernen.Aufzählungen erzeugen keine spröden Schnittstellen. Missbrauch von Aufzählungen tut.
Wofür sind Aufzählungen?
Aufzählungen sind so konzipiert, dass sie als Mengen von Konstanten mit aussagekräftigem Namen verwendet werden. Sie sind zu verwenden, wenn:
Gute Verwendungen von Aufzählungen:
System.DayOfWeek
) Sofern Sie nicht mit einem unglaublich undurchsichtigen Kalender zu tun haben, wird es immer nur 7 Wochentage geben.System.ConsoleColor
) Einige stimmen dem möglicherweise nicht zu, aber .Net hat sich aus einem bestimmten Grund dafür entschieden. Im Konsolensystem von .Net stehen der Konsole nur 16 Farben zur Verfügung. Diese 16 Farben entsprechen der älteren Farbpalette, die als CGA oder "Color Graphics Adapter" bezeichnet wird . Es werden nie neue Werte hinzugefügt, was bedeutet, dass dies tatsächlich eine sinnvolle Anwendung einer Aufzählung ist.Thread.State
) Die Entwickler von Java haben entschieden, dass es im Java-Threading-Modell immer nur einen festen Satz von Zuständen geben wird, in denen sich a befindenThread
kann. Um die Sache zu vereinfachen, werden diese verschiedenen Zustände als Aufzählungen dargestellt . Dies bedeutet, dass viele zustandsbasierte Prüfungen einfache ifs und Schalter sind, die in der Praxis mit ganzzahligen Werten arbeiten, ohne dass sich der Programmierer um die tatsächlichen Werte kümmern muss.System.Text.RegularExpressions.RegexOptions
) Bitflags sind eine sehr häufige Verwendung von Enums. In der Tat so verbreitet, dass in .Net alle Enums eine integrierteHasFlag(Enum flag)
Methode haben. Sie unterstützen auch die bitweisen Operatoren und es gibt einFlagsAttribute
, um eine Enumeration als einen Satz von Bitflags zu kennzeichnen. Wenn Sie eine Aufzählung als Satz von Flags verwenden, können Sie eine Gruppe von Booleschen Werten in einem einzelnen Wert darstellen und die Flags der Einfachheit halber klar benennen. Dies wäre äußerst vorteilhaft für die Darstellung der Flags eines Statusregisters in einem Emulator oder für die Darstellung der Berechtigungen einer Datei (Lesen, Schreiben, Ausführen) oder in nahezu jeder Situation, in der sich eine Reihe verwandter Optionen nicht gegenseitig ausschließen.Schlechte Verwendung von Aufzählungen:
True
Flagge impliziert diesFalse
). Dieses Verhalten kann durch die Verwendung spezieller Funktionen (dhIsTrue(flags)
undIsFalse(flags)
) weiter unterstützt werden .quelle
RegexOptions
msdn.microsoft.com/en-us/library/...Aufzählungen sind eine große Verbesserung gegenüber magischen Identifikationsnummern für geschlossene Mengen von Werten, denen nicht viele Funktionen zugeordnet sind. Normalerweise ist es Ihnen egal, welche Nummer tatsächlich mit der Aufzählung verknüpft ist. In diesem Fall ist es einfach, durch Hinzufügen neuer Einträge am Ende zu erweitern, es sollte keine Sprödigkeit auftreten.
Das Problem liegt vor, wenn Sie über umfangreiche Funktionen verfügen, die mit der Aufzählung verknüpft sind. Das heißt, Sie haben diese Art von Code herumliegen:
Ein oder zwei davon sind in Ordnung, aber sobald Sie diese Art von aussagekräftiger Aufzählung haben,
switch
neigen diese Aussagen dazu, sich wie Tribbles zu vermehren. Jetzt müssen Sie jedes Mal, wenn Sie die Aufzählung erweitern, alle dazugehörigenswitch
Einträge suchen, um sicherzustellen, dass Sie alles abgedeckt haben. Das ist spröde. Quellen wie Clean Code schlagen vor, dass Sieswitch
höchstens eine pro Enumeration haben sollten .In diesem Fall sollten Sie stattdessen die OO-Prinzipien verwenden und eine Schnittstelle für diesen Typ erstellen . Möglicherweise behalten Sie die Aufzählung für die Kommunikation dieses Typs bei, aber sobald Sie etwas damit tun müssen, erstellen Sie ein mit der Aufzählung verknüpftes Objekt, möglicherweise unter Verwendung einer Factory. Dies ist viel einfacher zu warten, da Sie nur einen Ort für die Aktualisierung suchen müssen: Ihre Factory, und indem Sie eine neue Klasse hinzufügen.
quelle
Wenn Sie vorsichtig sind, wie Sie sie verwenden, würde ich Enums nicht als schädlich betrachten. Es gibt jedoch ein paar Dinge zu beachten, wenn Sie sie in einem Bibliothekscode verwenden möchten, im Gegensatz zu einer einzelnen Anwendung.
Niemals Werte entfernen oder neu anordnen. Wenn Ihre Liste irgendwann einen Aufzählungswert enthält, sollte dieser Wert für alle Ewigkeit mit diesem Namen verknüpft sein. Wenn Sie möchten, können Sie Werte zu einem bestimmten Zeitpunkt umbenennen. Wenn Sie sie
deprecated_orc
jedoch nicht entfernen, können Sie die Kompatibilität mit altem Code, der mit den alten Aufzählungen kompiliert wurde, einfacher aufrechterhalten. Wenn ein neuer Code nicht mit alten Enum-Konstanten umgehen kann, generieren Sie entweder dort einen geeigneten Fehler oder stellen Sie sicher, dass kein solcher Wert diesen Codeteil erreicht.Rechne nicht mit ihnen. Führen Sie insbesondere keine Bestellvergleiche durch. Warum? Denn dann könnten Sie in eine Situation geraten, in der Sie vorhandene Werte nicht beibehalten und gleichzeitig eine vernünftige Reihenfolge beibehalten können. Nehmen Sie zum Beispiel eine Aufzählung für Kompassrichtungen: N = 0, NE = 1, E = 2, SE = 3,… Wenn Sie nach einer Aktualisierung NNE usw. zwischen den vorhandenen Richtungen einfügen, können Sie diese entweder am Ende hinzufügen der Liste und damit die Reihenfolge zu brechen, oder Sie verschachteln sie mit vorhandenen Schlüsseln und brechen damit etablierte Zuordnungen im Legacy-Code. Oder Sie verwerfen alle alten Schlüssel und haben einen komplett neuen Schlüsselsatz zusammen mit einem Kompatibilitätscode, der aus Gründen des Legacy-Codes zwischen alten und neuen Schlüsseln übersetzt.
Wählen Sie eine ausreichende Größe. Standardmäßig verwendet der Compiler die kleinste Ganzzahl, die alle Aufzählungswerte enthalten kann. Das bedeutet, dass Sie plötzlich 2 Byte für jeden Aufzählungswert anstelle von einem benötigen, wenn Ihr Satz möglicher Aufzählungen bei einem Update von 254 auf 259 erweitert wird. Dies könnte die Struktur- und Klassenlayouts überall zerstören. Versuchen Sie daher, dies zu vermeiden, indem Sie im ersten Entwurf eine ausreichende Größe verwenden. C ++ 11 gibt Ihnen hier viel Kontrolle, aber ansonsten sollte die Angabe eines Eintrags ebenfalls
LAST_SPECIES_VALUE=65535
hilfreich sein.Habe eine zentrale Registry. Da Sie die Zuordnung zwischen Namen und Werten korrigieren möchten, ist es schlecht, wenn Sie zulassen, dass Drittbenutzer Ihres Codes neue Konstanten hinzufügen. Kein Projekt, das Ihren Code verwendet, sollte berechtigt sein, diesen Header zu ändern, um neue Zuordnungen hinzuzufügen. Stattdessen sollten sie dich nerven, um sie hinzuzufügen. Dies bedeutet, dass für das von Ihnen erwähnte Beispiel von Mensch und Zwerg Aufzählungen in der Tat schlecht passen. Dort ist es besser, zur Laufzeit eine Art Registrierung zu haben, in der Benutzer Ihres Codes eine Zeichenfolge einfügen und eine eindeutige Zahl zurückerhalten können, die in einen undurchsichtigen Typ eingepackt ist. Die "Zahl" könnte sogar ein Zeiger auf die betreffende Zeichenfolge sein, es spielt keine Rolle.
Trotz der Tatsache, dass mein letzter Punkt oben Aufzählungen für Ihre hypothetische Situation schlecht geeignet macht, scheint Ihre tatsächliche Situation, in der sich einige Spezifikationen ändern könnten und Sie Code aktualisieren müssten, recht gut zu einer zentralen Registrierung für Ihre Bibliothek zu passen. Da sollten also Aufzählungen angebracht sein, wenn Sie sich meine anderen Vorschläge zu Herzen nehmen.
quelle