Null-Zeiger vs. Null-Objektmuster

27

Namensnennung: Dies ist aus einer verwandten P.SE-Frage hervorgegangen

Mein Hintergrund ist C / C ++, aber ich habe ziemlich viel in Java gearbeitet und codiere derzeit C #. Aufgrund meines C-Hintergrunds ist das Überprüfen von übergebenen und zurückgegebenen Zeigern aus zweiter Hand, aber ich erkenne an, dass dies meine Sichtweise verzerrt.

Kürzlich wurde das Null-Objektmuster erwähnt, bei dem die Idee besteht, dass ein Objekt immer zurückgegeben wird. Normaler Fall gibt das erwartete, aufgefüllte Objekt zurück und der Fehlerfall gibt ein leeres Objekt anstelle eines Nullzeigers zurück. Die Voraussetzung ist, dass die aufrufende Funktion immer ein Objekt hat, auf das zugegriffen werden kann, und daher Verstöße gegen den Nullzugriffsspeicher zu vermeiden.

  • Was sind die Vor- / Nachteile einer Nullprüfung gegenüber der Verwendung des Nullobjektmusters?

Ich kann mit dem NOP saubereren Aufrufcode sehen, aber ich kann auch sehen, wo es versteckte Fehler verursachen würde, die sonst nicht ausgelöst werden. Ich würde es vorziehen, wenn meine Anwendung während der Entwicklung hart ausfällt (auch bekannt als Ausnahme), anstatt einen stillen Fehler zu machen.

  • Kann das Null-Objektmuster nicht ähnliche Probleme haben wie das Nichtdurchführen einer Nullprüfung?

Viele der Objekte, mit denen ich gearbeitet habe, enthalten Objekte oder eigene Container. Es scheint, als müsste ich einen Sonderfall haben, um zu gewährleisten, dass alle Container des Hauptobjekts eigene leere Objekte hatten. Scheint so, als könnte dies bei mehreren Nesting-Schichten hässlich werden.

Gemeinschaft
quelle
Nicht der "Fehlerfall", sondern "in jedem Fall ein gültiges Objekt".
@ ThorbjørnRavnAndersen - guter Punkt, und ich habe die Frage bearbeitet, um das zu reflektieren. Bitte lassen Sie mich wissen, wenn ich die Prämisse von NOP immer noch falsch wiedergebe.
6
Die kurze Antwort lautet: "Null-Objektmuster" ist eine falsche Bezeichnung. Es wird genauer das "Nullobjekt-Anti-Muster" genannt. Mit einem "Fehlerobjekt", einem gültigen Objekt, das die gesamte Schnittstelle der Klasse implementiert, ist man normalerweise besser dran, aber jede Verwendung führt zu einem lauten, plötzlichen Tod.
Jerry Coffin
1
@JerryCoffin dieser Fall sollte durch eine Ausnahme angegeben werden. Die Idee ist, dass Sie niemals eine Null erhalten können und daher nicht nach Null suchen müssen.
1
Ich mag dieses "Muster" nicht. Möglicherweise ist es jedoch in überbeschränkten relationalen Datenbanken verwurzelt, in denen es einfacher ist, beim Staging von bearbeitbaren Daten auf spezielle "Nullzeilen" in Fremdschlüsseln zu verweisen, bevor die endgültige Aktualisierung ausgeführt wird, wenn alle Fremdschlüssel nicht vollständig Null sind.

Antworten:

24

Sie würden kein Null-Objektmuster an Stellen verwenden, an denen Null (oder Null-Objekt) zurückgegeben wird, weil ein katastrophaler Fehler aufgetreten ist. An diesen Stellen würde ich weiterhin null zurückgeben. In einigen Fällen können Sie auch abstürzen, wenn keine Wiederherstellung erfolgt, da zumindest der Absturzspeicherauszug genau angibt, wo das Problem aufgetreten ist. In solchen Fällen, wenn Sie Ihre eigene Fehlerbehandlung hinzufügen, werden Sie den Prozess immer noch abbrechen (wie ich bereits sagte, in Fällen, in denen es keine Wiederherstellung gibt), aber Ihre Fehlerbehandlung maskiert sehr wichtige Informationen, die ein Absturzabbild geliefert hätte.

Null-Objektmuster ist eher für Orte gedacht, an denen ein Standardverhalten vorliegt, das in einem Fall auftreten kann, in dem ein Objekt nicht gefunden wird. Betrachten Sie zum Beispiel Folgendes:

User* pUser = GetUser( "Bob" );

if( pUser )
{
    pUser->SetAddress( "123 Fake St." );
}

Wenn Sie NOP verwenden, würden Sie schreiben:

GetUser( "Bob" )->SetAddress( "123 Fake St." );

Beachten Sie, dass sich dieser Code wie folgt verhält: "Wenn Bob existiert, möchte ich seine Adresse aktualisieren." Wenn für Ihre Anwendung die Anwesenheit von Bob erforderlich ist, möchten Sie offensichtlich nicht automatisch erfolgreich sein. Es gibt jedoch Fälle, in denen diese Art von Verhalten angemessen wäre. Und produziert NOP in solchen Fällen keinen saubereren und präziseren Code?

An Orten, an denen Sie wirklich nicht ohne Bob leben können, würde GetUser () eine Anwendungsausnahme auslösen (dh keine Zugriffsverletzung oder ähnliches), die auf einer höheren Ebene behandelt wird und einen allgemeinen Betriebsfehler meldet. In diesem Fall ist NOP nicht erforderlich, es muss jedoch auch nicht explizit nach NULL gesucht werden. IMO, diese Überprüfungen auf NULL, vergrößern nur den Code und beeinträchtigen die Lesbarkeit. Auf NULL prüfen ist für einige Schnittstellen immer noch die richtige Design-Wahl, aber bei weitem nicht so viele, wie manche denken.

DXM
quelle
4
Stimmen Sie dem Anwendungsfall für Nullobjektmuster zu. Aber warum null zurückgeben, wenn katastrophale Fehler auftreten? Warum keine Ausnahmen werfen?
Apoorv Khurasia
2
@ MonsterTruck: umgekehrt. Wenn ich Code schreibe, stelle ich sicher, dass der Großteil der von externen Komponenten empfangenen Eingaben überprüft wird. Wenn ich jedoch zwischen internen Klassen Code so schreibe, dass eine Funktion einer Klasse niemals NULL zurückgibt, füge ich auf der aufrufenden Seite keine Nullprüfung hinzu, nur um "sicherer" zu sein. Der Wert kann möglicherweise NULL sein, wenn in meiner Anwendung ein schwerwiegender Logikfehler aufgetreten ist. In diesem Fall möchte ich, dass mein Programm genau an dieser Stelle abstürzt, da ich dann eine schöne Absturzabbilddatei erhalte und den Stapel- und Objektstatus rückgängig mache, um den Logikfehler zu identifizieren.
DXM
2
@MonsterTruck: mit "umkehren" meine ich, nicht explizit NULL zurückgeben, wenn Sie einen katastrophalen Fehler feststellen, sondern NULL nur aufgrund eines unvorhergesehenen katastrophalen Fehlers erwarten.
DXM
Ich verstehe jetzt, was Sie unter einem katastrophalen Fehler verstanden haben - etwas, das dazu führt, dass die Objektzuweisung selbst fehlschlägt. Recht? Bearbeiten: @ DXM kann nicht ausgeführt werden, da Ihr Spitzname nur 3 Zeichen lang ist.
Apoorv Khurasia
@ MonsterTruck: komisch über "@" Sache. Ich weiß, dass andere es schon getan haben. Ich frage mich, ob sie die Website vor kurzem optimiert haben. Es muss nicht Zuteilung sein. Wenn ich eine Klasse schreibe und die Absicht der API ist, immer ein gültiges Objekt zurückzugeben, würde ich den Aufrufer nicht dazu bringen, eine Nullprüfung durchzuführen. Ein katastrophaler Fehler kann jedoch ein Programmiererfehler sein (ein Tippfehler, durch den NULL zurückgegeben wurde) oder eine Racebedingung, durch die ein Objekt vor seiner Zeit gelöscht wurde, oder eine Stapelbeschädigung. Im Wesentlichen jeder unerwartete Codepfad, der zur Rückgabe von NULL führte. Wenn das passiert, wollen Sie ...
DXM
7

Was sind die Vor- / Nachteile einer Nullprüfung gegenüber der Verwendung des Nullobjektmusters?

Vorteile

  • Eine Nullprüfung ist besser, da sie mehr Fälle löst. Nicht alle Objekte weisen ein vernünftiges Standard- oder No-Op-Verhalten auf.
  • Eine Nullprüfung ist solider. Sogar Objekte mit vernünftigen Standardeinstellungen werden an Stellen verwendet, an denen die vernünftigen Standardeinstellungen nicht gültig sind. Code sollte in der Nähe der Hauptursache fehlschlagen, wenn er fehlschlagen wird. Code sollte offensichtlich fehlschlagen, wenn es fehlschlagen wird.

Nachteile

  • Vernünftige Standardeinstellungen führen normalerweise zu saubererem Code.
  • Vernünftige Standardeinstellungen führen normalerweise zu weniger katastrophalen Fehlern, wenn sie in die Wildnis gelangen.

Dieser letzte "Profi" ist nach meiner Erfahrung das Hauptunterscheidungsmerkmal, wann jeder angewendet werden sollte. Msgstr "Sollte der Fehler laut sein?" In einigen Fällen möchten Sie, dass ein Fehler hart und sofort auftritt. wenn ein Szenario, das niemals passieren sollte, es irgendwie tut. Wenn eine wichtige Ressource nicht gefunden wurde ... usw. In einigen Fällen ist eine vernünftige Standardeinstellung in Ordnung, da es sich nicht wirklich um einen Fehler handelt : Abrufen eines Werts aus einem Wörterbuch, aber der Schlüssel fehlt beispielsweise.

Wie bei jeder anderen Designentscheidung gibt es je nach Ihren Anforderungen Vor- und Nachteile.

Telastyn
quelle
1
Im Abschnitt "Profis": Stimmen Sie dem ersten Punkt zu. Der zweite Punkt ist die Schuld der Designer, weil sogar null verwendet werden kann, wo es nicht sein sollte. Sagt nichts Schlechtes über das Muster aus, sondern spiegelt nur den Entwickler wider, der das Muster falsch verwendet hat.
Apoorv Khurasia
Was ist mehr katastrophales Versagen, nicht in der Lage zu sein, Geld zu senden oder Geld zu senden (und somit nicht mehr zu haben), ohne dass der Empfänger es empfängt und es vor der expliziten Prüfung nicht kennt?
user470365
Nachteile: Mit vernünftigen Standardeinstellungen können neue Objekte erstellt werden. Wenn ein Nicht-Null-Objekt zurückgegeben wird, können einige seiner Attribute beim Erstellen eines anderen Objekts geändert werden. Wenn ein Nullobjekt zurückgegeben wird, kann es zum Erstellen eines neuen Objekts verwendet werden. In beiden Fällen funktioniert derselbe Code. Es ist kein separater Code zum Ändern der Kopie und für neue Codes erforderlich. Dies ist Teil der Philosophie, dass jede Codezeile ein anderer Ort für Fehler ist. Durch das Reduzieren der Zeilen werden Fehler reduziert.
Shawnhcorey
@shawnhcorey - was?
Telastyn
Ein Null-Objekt sollte verwendbar sein, als ob es ein reales Objekt wäre. Betrachten Sie beispielsweise das NaN von IEEE (keine Zahl). Wenn eine Gleichung schief geht, wird sie zurückgegeben. Und es kann für weitere Berechnungen verwendet werden, da jede Operation darauf NaN zurückgibt. Dies vereinfacht den Code, da Sie nicht nach jeder arithmetischen Operation nach NaN suchen müssen.
Shawnhcorey
6

Ich bin keine gute Quelle für objektive Informationen, aber subjektiv:

  • Ich möchte nicht, dass mein System jemals stirbt. Für mich ist es ein Zeichen für ein schlecht entworfenes oder unvollständiges System. Das gesamte System sollte alle möglichen Fälle behandeln und niemals in einen unerwarteten Zustand geraten. Um dies zu erreichen, muss ich alle Abläufe und Situationen explizit modellieren. Die Verwendung von Nullen ist ohnehin nicht explizit und gruppiert viele verschiedene Fälle unter einer Umrella. Ich bevorzuge NonExistingUser-Objekt zu null, ich bevorzuge NaN zu null, ... ein solcher Ansatz ermöglicht es mir, explizit über diese Situationen und ihre Behandlung in so vielen Details zu sein, wie ich möchte, während null mir nur die Option null lässt; In einer solchen Umgebung wie Java kann jedes Objekt null sein. Warum möchten Sie sich in diesem generischen Pool von Fällen also lieber auch Ihren speziellen Fall verstecken?
  • null-Implementierungen haben ein drastisch anderes Verhalten als object und sind meistens an die Menge aller Objekte gebunden (warum? warum? warum?); Für mich scheint ein derart drastischer Unterschied das Ergebnis der Nullen zu sein, was ein Hinweis auf einen Fehler ist. In diesem Fall stirbt das System höchstwahrscheinlich (ich gehe davon aus, dass so viele Leute es tatsächlich vorziehen, dass ihr System in den oben genannten Posts stirbt), sofern nicht ausdrücklich darauf eingegangen wird. Für mich ist das ein fehlerhafter Ansatz in seiner Wurzel - es ermöglicht dem System, standardmäßig zu sterben, es sei denn, Sie haben sich ausdrücklich darum gekümmert, das ist nicht sehr sicher, oder? In jedem Fall ist es jedoch unmöglich, einen Code zu schreiben, der den Wert null und object auf dieselbe Weise behandelt - nur wenige grundlegende eingebaute Operatoren (assign, equal, param usw.) funktionieren, der gesamte andere Code schlägt einfach fehl und Sie benötigen zwei völlig verschiedene Wege;
  • Verwenden Sie die Skalierung von Nullobjekten besser - Sie können so viele Informationen und Strukturen hinzufügen, wie Sie möchten. NonExistingUser ist ein sehr gutes Beispiel - es kann eine E-Mail des Benutzers enthalten, den Sie abrufen wollten, und den Vorschlag enthalten, basierend auf diesen Informationen einen neuen Benutzer zu erstellen. Bei der Null-Lösung müsste ich jedoch überlegen, wie die E-Mail, auf die die Benutzer zugreifen wollten, beibehalten werden soll nahe an der Null-Ergebnisbehandlung;

In Umgebungen wie Java oder C # bietet dies nicht den Hauptvorteil von Explicitness - die Sicherheit der Lösung ist vollständig, da Sie an der Stelle eines Objekts im System immer noch Null erhalten können und Java keine Mittel hat (außer benutzerdefinierten Annotationen für Beans) , etc.), um Sie davor zu schützen, mit Ausnahme expliziter Ifs. In einem System, das frei von Null-Implementierungen ist, bietet eine solche Annäherung an die Problemlösung (mit Objekten, die Ausnahmefälle darstellen) alle genannten Vorteile. Versuchen Sie also, etwas anderes als die gängigen Sprachen zu lesen, und Sie werden feststellen, dass Sie Ihre Codierungsmethoden ändern.

Im Allgemeinen werden Null- / Fehlerobjekte / Rückgabetypen als sehr solide Fehlerbehandlungsstrategie und recht mathematisch fundiert angesehen, während die Ausnahmebehandlungsstrategie von Java oder C nur einen Schritt über der "die ASAP" -Strategie liegt - lassen Sie also im Grunde die Bürde von Instabilität und Unvorhersehbarkeit des Systems auf Entwickler oder Betreuer.

Es gibt auch Monaden, die dieser Strategie überlegen sind (auch in Bezug auf die Komplexität des Verständnisses zunächst überlegen), aber diese betreffen eher die Fälle, in denen Sie externe Aspekte des Typs modellieren müssen, während Null Object interne Aspekte modelliert. Daher ist NonExistingUser wahrscheinlich besser als monad maybe_existing modelliert.

Und schließlich glaube ich nicht, dass es einen Zusammenhang zwischen dem Null-Objektmuster und der stillen Behandlung von Fehlersituationen gibt. Es sollte anders herum sein - es sollte Sie zwingen, jeden einzelnen Fehlerfall explizit zu modellieren und entsprechend zu behandeln. Jetzt könnten Sie sich natürlich dafür entscheiden, schlampig zu sein (aus welchem ​​Grund auch immer) und die Behandlung des Fehlerfalls explizit auslassen, aber generisch behandeln - nun, das war Ihre explizite Wahl.

Bohdan Tsymbala
quelle
9
Der Grund, warum meine Anwendung fehlschlägt, wenn etwas Unerwartetes passiert, ist, dass etwas Unerwartetes passiert ist. Wie ist es passiert? Warum? Ich erhalte einen Crash-Dump und kann ein potenziell gefährliches Problem untersuchen und beheben, anstatt es im Code verbergen zu lassen. In C # kann ich natürlich alle unerwarteten Ausnahmen abfangen, um zu versuchen, die Anwendung wiederherzustellen, aber selbst dann möchte ich den Ort protokollieren, an dem das Problem aufgetreten ist, und herausfinden, ob eine separate Behandlung erforderlich ist (auch wenn dies nicht der Fall ist) protokolliere diesen Fehler, er ist tatsächlich erwartet und behebbar ").
Luaan
3

Ich finde es interessant festzustellen, dass die Lebensfähigkeit eines Null-Objekts ein wenig anders zu sein scheint, je nachdem, ob Ihre Sprache statisch typisiert ist oder nicht. Ruby ist eine Sprache, die ein Objekt für Null (oder Nilin der Terminologie der Sprache) implementiert .

Anstatt einen Zeiger auf den nicht initialisierten Speicher zurückzugeben, gibt die Sprache das Objekt zurück Nil. Dies wird durch die dynamische Typisierung der Sprache erleichtert. Es ist nicht garantiert, dass eine Funktion immer ein zurückgibt int. Es kann zurückkehren, Nilwenn es das ist, was es tun muss. Es wird komplizierter und schwieriger, dies in einer statisch typisierten Sprache zu tun, da Sie natürlich erwarten, dass null auf jedes Objekt anwendbar ist, das als Referenz aufgerufen werden kann. Sie müssten damit beginnen, Nullversionen aller Objekte zu implementieren, die Null sein könnten (oder die Scala / Haskell-Route einschlagen und eine Art "Vielleicht" -Hülle haben).

MrLister hat darauf hingewiesen, dass Sie in C ++ beim erstmaligen Erstellen keinen Initialisierungsverweis / Zeiger haben. Indem Sie ein dediziertes Null-Objekt anstelle eines rohen Null-Zeigers haben, können Sie einige nette zusätzliche Funktionen erhalten. Ruby's Nilenthält eine to_s(toString) -Funktion, die zu leerem Text führt. Dies ist großartig, wenn es Ihnen nichts ausmacht, eine Null-Rückgabe für einen String zu erhalten, und Sie die Berichterstellung überspringen möchten, ohne einen Absturz zu verursachen. Mit Open Classes können Sie auch die Ausgabe von Nilnotfalls manuell überschreiben .

Zugegeben, dies kann alles mit einem Körnchen Salz eingenommen werden. Ich glaube, dass in einer dynamischen Sprache das Null-Objekt eine große Bequemlichkeit sein kann, wenn es richtig verwendet wird. Um das Beste aus dem Programm herauszuholen, müssen Sie möglicherweise die Basisfunktionalität bearbeiten. Dies kann dazu führen, dass Ihr Programm für Benutzer, die mit den Standardformularen arbeiten, nur schwer zu verstehen und zu warten ist.

KChaloux
quelle
1

Schauen Sie sich für einen zusätzlichen Standpunkt Objective-C an, bei dem anstelle eines Null-Objekts der Wert nil wie ein Objekt funktioniert. Jede Nachricht, die an ein Null-Objekt gesendet wird, hat keine Auswirkung und gibt den Wert 0 / 0.0 / NO (false) / NULL / nil zurück, unabhängig vom Rückgabewerttyp der Methode. Dies wird in der MacOS X- und iOS-Programmierung als weit verbreitetes Muster verwendet. In der Regel werden Methoden so entworfen, dass ein Null-Objekt, das 0 zurückgibt, ein vernünftiges Ergebnis ist.

Wenn Sie beispielsweise überprüfen möchten, ob eine Zeichenfolge ein oder mehrere Zeichen enthält, können Sie schreiben

aString.length > 0

und erhalten Sie ein angemessenes Ergebnis, wenn aString == nil, weil eine nicht vorhandene Zeichenfolge keine Zeichen enthält. Die Leute brauchen eine Weile, um sich daran zu gewöhnen, aber ich würde wahrscheinlich eine Weile brauchen, um mich daran zu gewöhnen, überall in anderen Sprachen nach Nullwerten zu suchen.

(Es gibt auch ein Singleton-Objekt der Klasse NSNull, das sich ganz anders verhält und in der Praxis nicht sehr häufig verwendet wird.)

gnasher729
quelle
Objective-C hat das Null Object / Null Pointer-Objekt wirklich verschwommen gemacht. nilget #define'd to NULL und verhält sich wie ein Null-Objekt. Dies ist eine Laufzeitfunktion, während in C # / Java Nullzeiger Fehler ausgeben.
Maxthon Chan
0

Verwenden Sie auch nicht, wenn Sie dies vermeiden können. Verwenden Sie eine Factory-Funktion, die einen Optionstyp, ein Tupel oder einen dedizierten Summentyp zurückgibt. Mit anderen Worten, repräsentieren Sie einen potenziell standardmäßigen Wert mit einem anderen Typ als einen garantierten, nicht standardmäßigen Wert.

Lassen Sie uns zuerst die Desiderata auflisten und dann darüber nachdenken, wie wir dies in einigen Sprachen ausdrücken können (C ++, OCaml, Python).

  1. versehentliche Verwendung eines Standardobjekts zur Kompilierungszeit abfangen.
  2. Machen Sie beim Lesen des Codes deutlich, ob ein bestimmter Wert möglicherweise den Standardwert hat oder nicht.
  3. Wählen Sie unseren sinnvollen Standardwert, falls zutreffend, einmal pro Typ . Wählen Sie auf keinen Fall an jedem Anrufstandort einen potenziell anderen Standard aus .
  4. Erleichtern Sie statischen Analysewerkzeugen oder Menschen grepdie Suche nach möglichen Fehlern.
  5. Bei einigen Anwendungen sollte das Programm normal fortgesetzt werden, wenn unerwartet ein Standardwert angegeben wird. Bei anderen Anwendungen sollte das Programm sofort angehalten werden, wenn ein Standardwert angegeben wurde, idealerweise auf informative Weise.

Ich denke, die Spannung zwischen dem Nullobjektmuster und den Nullzeigern kommt von (5). Wenn wir Fehler jedoch früh genug erkennen können, wird (5) streitig.

Betrachten wir diese Sprache nach Sprache:

C ++

Meiner Meinung nach sollte eine C ++ - Klasse im Allgemeinen standardmäßig konstruierbar sein, da sie die Interaktion mit Bibliotheken erleichtert und die Verwendung der Klasse in Containern erleichtert. Dies vereinfacht auch die Vererbung, da Sie nicht darüber nachdenken müssen, welcher Superklassenkonstruktor aufgerufen werden soll.

Dies bedeutet jedoch, dass Sie nicht sicher wissen können, ob sich ein Wert vom Typ MyClassim "Standardzustand" befindet oder nicht. Abgesehen davon, dass Sie bool nonemptyzur Laufzeit ein oder ein ähnliches Feld für die Oberflächen-Standardisierung eingeben, können Sie neue Instanzen von so erstellen MyClass, dass der Benutzer gezwungen ist, dies zu überprüfen .

Ich würde empfehlen, eine Factory-Funktion zu verwenden, die einen oder, wenn möglich std::optional<MyClass>, std::pair<bool, MyClass>einen r-Wert-Verweis auf einen zurückgibt std::unique_ptr<MyClass>.

Wenn Sie möchten, dass Ihre Factory-Funktion einen anderen "Platzhalter" -Zustand als den standardmäßig erstellten zurückgibt MyClass, verwenden Sie a, std::pairund vergewissern Sie sich, dass Ihre Funktion dies tut.

Wenn die Werksfunktion einen eindeutigen Namen hat, ist es einfach, grepnach dem Namen zu suchen und nach Schlampigkeit zu suchen. Es ist jedoch schwierig, grepFälle zu ermitteln, in denen der Programmierer die Werksfunktion hätte verwenden sollen, dies jedoch nicht tat .

OCaml

Wenn Sie eine Sprache wie OCaml verwenden, können Sie einfach einen optionTyp (in der OCaml-Standardbibliothek) oder einen eitherTyp (nicht in der Standardbibliothek, aber einfach zu rollen) verwenden. Oder ein defaultableTyp (ich erfinde den Begriff defaultable).

type 'a option =
    | None
    | Some of 'a

type ('a, 'b) either =
    | Left of 'a
    | Right of 'b

type 'a defaultable =
    | Default of 'a
    | Real of 'a

Ein defaultablewie oben gezeigtes ist besser als ein Paar, da der Benutzer eine Musterübereinstimmung durchführen muss, um das zu extrahieren, 'aund das erste Element des Paares nicht einfach ignorieren kann.

Das defaultableoben gezeigte Äquivalent des Typs kann in C ++ unter Verwendung von a std::variantmit zwei Instanzen desselben Typs verwendet werden, Teile der std::variantAPI können jedoch nicht verwendet werden, wenn beide Typen identisch sind. Es ist auch eine seltsame Verwendung, std::variantda seine Typkonstruktoren unbenannt sind.

Python

Sie erhalten ohnehin keine Überprüfung zur Kompilierungszeit für Python. Bei einer dynamischen Typisierung gibt es jedoch im Allgemeinen keine Umstände, unter denen Sie eine Platzhalterinstanz eines bestimmten Typs benötigen, um den Compiler zu platzieren.

Ich würde nur empfehlen, eine Ausnahme auszulösen, wenn Sie gezwungen wären, eine Standardinstanz zu erstellen.

Wenn dies nicht akzeptabel ist, würde ich empfehlen, ein zu erstellen DefaultedMyClass, MyClassdas von Ihrer werkseitigen Funktion erbt, und dieses zurückzusenden. Das verschafft Ihnen Flexibilität in Bezug auf die "Stubbing" -Funktionalität in Standardinstanzen, wenn Sie dies benötigen.

Gregory Nisbet
quelle