Ich habe einem Kollegen beschrieben, warum ein Konstruktor, der eine Methode aufruft, ein Antipattern sein kann.
Beispiel (in meinem rostigen C ++)
class C {
public :
C(int foo);
void setFoo(int foo);
private:
int foo;
}
C::C(int foo) {
setFoo(foo);
}
void C::setFoo(int foo) {
this->foo = foo
}
Ich möchte diese Tatsache durch Ihren zusätzlichen Beitrag besser motivieren. Wenn Sie Beispiele, Buchreferenzen, Blogseiten oder Namen von Prinzipien haben, wären sie sehr willkommen.
Edit: Ich spreche im Allgemeinen, aber wir codieren in Python.
this
an eine der vom Konstruktor aufgerufenen Methoden.Antworten:
Sie haben keine Sprache angegeben.
In C ++ muss ein Konstruktor beim Aufrufen einer virtuellen Funktion aufpassen, dass die eigentliche Funktion, die er aufruft, die Klassenimplementierung ist. Wenn es sich um eine rein virtuelle Methode ohne Implementierung handelt, liegt eine Zugriffsverletzung vor.
Ein Konstruktor kann nicht virtuelle Funktionen aufrufen.
Wenn Ihre Sprache Java ist und die Funktionen standardmäßig virtuell sind, ist es sinnvoll, besonders vorsichtig zu sein.
C # scheint die Situation so zu handhaben, wie Sie es erwarten würden: Sie können virtuelle Methoden in Konstruktoren aufrufen und es ruft die endgültigste Version auf. Also in C # kein Anti-Pattern.
Ein häufiger Grund für den Aufruf von Methoden aus Konstruktoren besteht darin, dass mehrere Konstruktoren eine gemeinsame "init" -Methode aufrufen möchten.
Beachten Sie, dass Destruktoren dasselbe Problem mit virtuellen Methoden haben. Daher können Sie keine virtuelle "Aufräum" -Methode verwenden, die sich außerhalb Ihres Destruktors befindet und erwartet, dass sie vom Destruktor der Basisklasse aufgerufen wird.
Java und C # haben keine Destruktoren, sondern Finalizer. Ich kenne das Verhalten mit Java nicht.
C # scheint die Bereinigung in dieser Hinsicht korrekt zu handhaben.
(Beachten Sie, dass Java und C # zwar eine Garbage Collection haben, aber nur die Speicherzuordnung verwalten. Es gibt eine andere Bereinigung, die Ihr Destruktor durchführen muss, ohne Speicher freizugeben.)
quelle
OK, jetzt, da die Verwirrung zwischen Klassenmethoden und Instanzmethoden beseitigt ist, kann ich eine Antwort geben :-)
Das Problem besteht nicht darin, Instanzmethoden im Allgemeinen von einem Konstruktor aus aufzurufen. Es ist mit dem Aufruf von virtuellen Methoden (direkt oder indirekt). Der Hauptgrund ist, dass das Objekt im Konstruktor noch nicht vollständig konstruiert ist . Und vor allem die Unterklassenteile werden überhaupt nicht erstellt, während der Basisklassenkonstruktor ausgeführt wird. Daher ist sein interner Zustand in sprachabhängiger Weise inkonsistent, und dies kann in verschiedenen Sprachen verschiedene subtile Fehler verursachen.
C ++ und C # wurden bereits von anderen diskutiert. In Java wird die virtuelle Methode des am häufigsten abgeleiteten Typs aufgerufen, der Typ ist jedoch noch nicht initialisiert. Wenn diese Methode also Felder aus dem abgeleiteten Typ verwendet, sind diese Felder zu diesem Zeitpunkt möglicherweise noch nicht ordnungsgemäß initialisiert. Dieses Problem wird ausführlich in Effecive Java 2nd Edition , Punkt 17: Entwurf und Dokument zur Vererbung besprochen oder untersagt .
Beachten Sie, dass dies ein Sonderfall des allgemeinen Problems ist , Objektreferenzen vorzeitig zu veröffentlichen . Instanzmethoden haben einen impliziten
this
Parameter, diethis
explizite Übergabe an eine Methode kann jedoch ähnliche Probleme verursachen. Insbesondere in gleichzeitigen Programmen, in denen der Objektverweis vorzeitig auf einen anderen Thread veröffentlicht wird, kann dieser Thread bereits Methoden für ihn aufrufen, bevor der Konstruktor im ersten Thread fertig ist.quelle
Ich würde Methodenaufrufe hier nicht als Antimuster an sich betrachten, eher als Code-Geruch. Wenn eine Klasse eine
reset
Methode bereitstellt, die ein Objekt in den ursprünglichen Zustandreset()
zurückversetzt, lautet der Aufruf des Konstruktors DRY. (Ich mache keine Aussagen über Reset-Methoden).Hier ist ein Artikel, der dazu beitragen könnte, Ihren Appell an die Behörde zu erfüllen: http://misko.hevery.com/code-reviewers-guide/flaw-constructor-does-real-work/
Es geht nicht wirklich darum, Methoden aufzurufen, sondern um Konstruktoren, die zu viel bewirken. IMHO ist das Aufrufen von Methoden in einem Konstruktor ein Geruch, der möglicherweise darauf hinweist, dass ein Konstruktor zu schwer ist.
Dies hängt damit zusammen, wie einfach es ist, Ihren Code zu testen. Gründe sind:
Unit-Tests beinhalten viel Kreation und Zerstörung - daher sollte der Aufbau schnell gehen.
Je nachdem, was diese Methoden tun, kann es schwierig sein, einzelne Codeeinheiten zu testen, ohne sich auf eine im Konstruktor festgelegte (möglicherweise nicht testbare) Voraussetzung zu verlassen (z. B. Informationen von einem Netzwerk abrufen).
quelle
Philosophisch gesehen besteht der Zweck des Konstruktors darin, einen rohen Teil der Erinnerung in eine Instanz umzuwandeln. Während der Ausführung des Konstruktors ist das Objekt noch nicht vorhanden. Daher ist es eine schlechte Idee, seine Methoden aufzurufen. Sie wissen vielleicht doch nicht, was sie intern tun, und sie können zu Recht davon ausgehen, dass das Objekt zumindest existiert (duh!), Wenn sie aufgerufen werden.
In C ++ und insbesondere in Python ist es technisch gesehen möglicherweise nichts Falsches daran, dass Sie vorsichtig sein müssen.
In der Praxis sollten Sie Aufrufe nur auf solche Methoden beschränken, mit denen Klassenmitglieder initialisiert werden.
quelle
Es ist kein allgemeines Problem. Dies ist ein Problem in C ++, insbesondere bei der Verwendung von Vererbungs- und virtuellen Methoden, da die Objektkonstruktion rückwärts erfolgt und die vtable-Zeiger mit jeder Konstruktorebene in der Vererbungshierarchie zurückgesetzt werden. Wenn Sie also eine virtuelle Methode aufrufen, ist dies möglicherweise nicht der Fall Am Ende erhalten Sie diejenige, die tatsächlich der Klasse entspricht, die Sie erstellen möchten, was den gesamten Zweck der Verwendung virtueller Methoden zunichte macht.
In Sprachen mit vernünftiger OOP-Unterstützung, bei denen der vtable-Zeiger von Anfang an korrekt gesetzt wurde, besteht dieses Problem nicht.
quelle
Es gibt zwei Probleme beim Aufrufen einer Methode:
Es ist nichts Falsches daran, eine Hilfsfunktion aufzurufen, solange sie nicht in die beiden vorherigen Fälle fällt.
quelle
Ich kaufe das nicht. In einem objektorientierten System können Sie praktisch nur eine Methode aufrufen. Tatsächlich ist das mehr oder weniger die Definition von "objektorientiert". Also, wenn ein Konstruktor keine Methoden aufrufen kann, was kann er dann tun?
quelle
In der OOP-Theorie sollte es keine Rolle spielen, aber in der Praxis behandelt jede OOP-Programmiersprache unterschiedliche Konstruktoren . Ich verwende nicht sehr oft statische Methoden.
In C ++ & Delphi füge ich einige sekundäre Methoden als Erweiterung der Konstruktoren hinzu, wenn ich einigen Eigenschaften ("Feldelementen") Anfangswerte zuweisen musste und der Code sehr erweitert ist.
Und nenne keine anderen Methoden, die komplexere Dinge tun.
Bei den Eigenschaften "getters" und "setters" verwende ich normalerweise private / protected-Variablen zum Speichern ihres Status sowie die Methoden "getters" und "setters".
Im Konstruktor weise ich den Eigenschaftsstatusfeldern "Standard" -Werte zu, OHNE die "Accessoren" aufzurufen.
quelle