Ich diskutiere mit meinem Kollegen darüber, wie viel Arbeit ein Konstrukteur leisten kann. Ich habe eine Klasse B, die intern ein anderes Objekt A erfordert. Objekt A ist eines von wenigen Mitgliedern, die Klasse B für ihre Arbeit benötigt. Alle öffentlichen Methoden hängen vom internen Objekt A ab. Informationen zu Objekt A werden in der Datenbank gespeichert. Daher versuche ich, sie zu überprüfen und abzurufen, indem ich sie in der Datenbank im Konstruktor nachschaue. Mein Mitarbeiter wies darauf hin, dass der Konstruktor nur die Konstruktorparameter erfassen sollte. Da alle öffentlichen Methoden ohnehin fehlschlagen würden, wenn Objekt A nicht mit den Eingaben für den Konstruktor gefunden wird, habe ich argumentiert, dass es besser ist, eine Instanz früh im Konstruktor auszulösen, anstatt zuzulassen, dass sie erstellt und später fehlschlägt.
Was denken andere? Ich verwende C #, wenn das einen Unterschied macht.
Lesen Gibt es jemals einen Grund, die gesamte Arbeit eines Objekts in einem Konstruktor zu erledigen? Ich frage mich, ob das Abrufen von Objekt A durch Aufrufen von DB Teil der "sonstigen Initialisierung, die erforderlich ist, um das Objekt einsatzbereit zu machen" ist. Wenn der Benutzer falsche Werte an den Konstruktor übergibt, kann ich keine seiner öffentlichen Methoden verwenden.
Konstruktoren sollten die Felder eines Objekts instanziieren und alle anderen Initialisierungen vornehmen, die erforderlich sind, um das Objekt einsatzbereit zu machen. Dies bedeutet im Allgemeinen, dass Konstruktoren klein sind, aber es gibt Szenarien, in denen dies einen erheblichen Arbeitsaufwand bedeuten würde.
quelle
Antworten:
Ihre Frage besteht aus zwei völlig getrennten Teilen:
Sollte ich eine Ausnahme vom Konstruktor auslösen oder sollte die Methode fehlschlagen?
Dies ist eindeutig eine Anwendung des Fail-Fast- Prinzips. Das Fehlschlagen des Konstruktors ist viel einfacher zu debuggen, als herauszufinden, warum die Methode fehlschlägt. Beispielsweise kann es sein, dass Sie die Instanz bereits aus einem anderen Teil des Codes erstellt haben und beim Aufrufen von Methoden Fehler auftreten. Ist es offensichtlich, dass das Objekt falsch erstellt wurde? Nein.
Wie für das Problem "wickeln Sie den Anruf in try / catch". Ausnahmen sollen außergewöhnlich sein . Wenn Sie wissen, dass ein Code eine Ausnahme auslöst, schließen Sie den Code nicht in try / catch ein, sondern überprüfen die Parameter dieses Codes, bevor Sie den Code ausführen, der eine Ausnahme auslösen kann. Ausnahmen sollen nur sicherstellen, dass das System nicht in einen ungültigen Zustand gerät. Wenn Sie wissen, dass einige Eingabeparameter zu einem ungültigen Status führen können, stellen Sie sicher, dass diese Parameter niemals auftreten. Auf diese Weise müssen Sie nur an Stellen try / catch ausführen, an denen Sie die Ausnahme, die sich normalerweise an der Systemgrenze befindet, logisch behandeln können.
Kann ich vom Konstruktor aus auf "andere Teile des Systems wie DB" zugreifen?
Ich denke, das widerspricht dem Grundsatz des geringsten Erstaunens . Nicht viele Leute würden erwarten, dass der Konstruktor auf eine Datenbank zugreift. Also nein, das solltest du nicht tun.
quelle
Pfui.
Ein Konstruktor sollte so wenig wie möglich tun - das Problem besteht darin, dass das Ausnahmeverhalten in Konstruktoren umständlich ist. Nur sehr wenige Programmierer kennen das richtige Verhalten (einschließlich Vererbungsszenarien), und Ihre Benutzer dazu zu zwingen, jede einzelne Instanziierung zu versuchen / abzufangen, ist im besten Fall ... schmerzhaft.
Es gibt zwei Teile dazu in dem Konstruktor und mit dem Konstruktor. Es ist im Konstruktor umständlich, weil Sie dort nicht viel tun können, wenn eine Ausnahme auftritt. Sie können keinen anderen Typ zurückgeben. Sie können nicht null zurückgeben. Grundsätzlich können Sie die Ausnahme erneut auslösen, ein defektes Objekt (fehlerhafter Konstruktor) zurückgeben oder (im besten Fall) einen internen Teil durch einen vernünftigen Standard ersetzen. Die Verwendung des Konstruktors ist umständlich, da dann Ihre Instantiierungen (und die Instantiierungen aller abgeleiteten Typen!) Ausgelöst werden können. Dann können Sie sie nicht in Elementinitialisierern verwenden. Sie verwenden am Ende Ausnahmen für die Logik, um zu sehen, ob meine Erstellung erfolgreich war. Dies geht über die Lesbarkeit (und Zerbrechlichkeit) hinaus, die durch try / catch überall verursacht wird.
Wenn Ihr Konstruktor nicht triviale Dinge ausführt oder Dinge, von denen vernünftigerweise erwartet werden kann, dass sie fehlschlagen, sollten Sie
Create
stattdessen eine statische Methode (mit einem nicht öffentlichen Konstruktor) in Betracht ziehen . Es ist viel benutzerfreundlicher, einfacher zu debuggen und bietet Ihnen bei Erfolg eine vollständig konstruierte Instanz.quelle
Connection
ObjektCreateCommand
für Sie funktionieren kann , damit Sie wissen, dass der Befehl ordnungsgemäß verbunden ist.Das Gleichgewicht zwischen Konstruktor und Methodenkomplexität ist in der Tat eine Frage des guten Stils. Sehr oft wird ein leerer Konstruktor
Class() {}
verwendet, mit einer MethodeInit(..) {..}
für kompliziertere Dinge. Zu berücksichtigende Argumente:Class x = new Class(); x.Init(..);
mehrmals wiederholen , teilweise in Komponententests. Nicht so gut, aber glasklar zum Lesen. Manchmal füge ich sie einfach in eine Codezeile ein.quelle
init
Methode den Vorteil, dass die Handhabung von Fehlern viel einfacher ist. Insbesondere kann der Rückgabewert derinit
Methode angeben, ob die Initialisierung erfolgreich ist, und die Methode kann sicherstellen, dass sich das Objekt immer in einem guten Zustand befindet.