it-swarm.dev

Der Konstruktor sollte im Allgemeinen keine Methoden aufrufen

Ich habe einem Kollegen beschrieben, warum ein Konstruktor, der eine Methode aufruft, ein Antimuster 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, sind diese sehr willkommen.

Bearbeiten: Ich spreche im Allgemeinen, aber wir codieren in Python.

12
Stefano Borini

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, handelt es sich um eine Zugriffsverletzung.

Ein Konstruktor kann nicht virtuelle Funktionen aufrufen.

Wenn Ihre Sprache Java ist, wobei Funktionen im Allgemeinen standardmäßig virtuell sind, ist es sinnvoll, dass Sie besonders vorsichtig sein müssen.

C # scheint mit der Situation so umzugehen, 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 das Aufrufen von Methoden von Konstruktoren ist, dass Sie mehrere Konstruktoren haben, die eine gemeinsame "init" -Methode aufrufen möchten.

Beachten Sie, dass Destruktoren das gleiche Problem mit virtuellen Methoden haben. Daher können Sie keine virtuelle "Bereinigungs" -Methode haben, die sich außerhalb Ihres Destruktors befindet, und erwarten, dass sie vom Destruktor der Basisklasse aufgerufen wird.

Java und C # haben keine Destruktoren, sie haben Finalizer. Ich kenne das Verhalten mit Java nicht.

C # scheint diesbezüglich die Bereinigung korrekt durchzuführen.

(Beachten Sie, dass Java und C # haben eine Garbage Collection, die jedoch nur die Speicherzuweisung verwaltet. Es gibt eine andere Bereinigung, die Ihr Destruktor durchführen muss, die keinen Speicher freigibt).

26
CashCow

OK, jetzt, da die Verwirrung bezüglich Klassenmethoden vs Instanzmethoden beseitigt ist, kann ich eine Antwort geben :-)

Das Problem besteht nicht darin, Instanzmethoden im Allgemeinen von einem Konstruktor aus aufzurufen. Dies geschieht mit dem Aufruf von virtuellen Methoden (direkt oder indirekt). Der Hauptgrund ist, dass das Objekt im Konstruktor noch nicht vollständig konstruiert ist . Und insbesondere werden seine Unterklassenteile überhaupt nicht konstruiert, während der Basisklassenkonstruktor ausgeführt wird. Daher ist sein interner Zustand sprachabhängig inkonsistent, und dies kann verschiedene subtile Fehler in verschiedenen Sprachen verursachen.

C++ und C # wurden bereits von anderen diskutiert. In Java wird die virtuelle Methode des am meisten abgeleiteten Typs aufgerufen, dieser Typ ist jedoch noch nicht initialisiert. Wenn diese Methode also Felder des abgeleiteten Typs verwendet, werden 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 für die Vererbung oder verbietet es besprochen.

Beachten Sie, dass dies ein Sonderfall des allgemeinen Problems ist, dass Objektverweise vorzeitig veröffentlicht werden . Instanzmethoden haben einen impliziten Parameter this, aber die explizite Übergabe von this an eine Methode kann ähnliche Probleme verursachen. Insbesondere in gleichzeitigen Programmen, in denen die Objektreferenz vorzeitig in einem anderen Thread veröffentlicht wird, kann dieser Thread bereits Methoden aufrufen, bevor der Konstruktor im ersten Thread beendet ist.

18
Péter Török

Ich würde Methodenaufrufe hier nicht als Antimuster an sich betrachten, sondern eher als Codegeruch. Wenn eine Klasse eine reset -Methode bereitstellt, die ein Objekt in seinen ursprünglichen Zustand zurückversetzt, lautet der Aufruf von reset() im Konstruktor DRY. (Ich mache keine Aussagen über Reset-Methoden).

Hier ist ein Artikel, der dazu beitragen könnte, Ihren Appell an die Autorität zu befriedigen: 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 tun. 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:

  1. Unit-Tests beinhalten viel Schöpfung und Zerstörung - daher sollte der Bau schnell sein.

  2. Abhängig davon, was diese Methoden tun, kann es schwierig sein, diskrete Codeeinheiten zu testen, ohne sich auf eine (möglicherweise nicht testbare) Voraussetzung zu verlassen, die im Konstruktor eingerichtet wurde (z. B. Informationen von einem Netzwerk abrufen).

7
Paul Butcher

Philosophisch gesehen besteht der Zweck des Konstruktors darin, einen rohen Speicherblock in eine Instanz umzuwandeln. Während der Ausführung des Konstruktors existiert das Objekt noch nicht, 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.

Technisch gesehen kann daran nichts auszusetzen sein. In C++ und insbesondere in Python liegt es an Ihnen, vorsichtig zu sein.

In der Praxis sollten Sie Aufrufe nur auf solche Methoden beschränken, mit denen Klassenmitglieder initialisiert werden.

3
user17621

Es ist kein allgemeines Problem. Dies ist ein Problem in C++, insbesondere bei Verwendung von Vererbung 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 eine virtuelle Methode aufrufen, erhalten Sie möglicherweise nicht 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, die den vtable-Zeiger von Anfang an korrekt gesetzt haben, besteht dieses Problem nicht.

2
Mason Wheeler

Beim Aufrufen einer Methode gibt es zwei Probleme:

  • aufrufen einer virtuellen Methode, die entweder etwas Unerwartetes tun kann (C++) oder Teile der Objekte verwenden kann, die noch nicht initialisiert wurden
  • aufrufen einer öffentlichen Methode (die die Klasseninvarianten erzwingen soll), da das Objekt noch nicht unbedingt vollständig ist (und daher seine Invariante möglicherweise nicht gilt)

Es ist nichts Falsches daran, eine Hilfsfunktion aufzurufen, solange sie in den beiden vorherigen Fällen nicht fällt.

2
Matthieu M.

Ich kaufe das nicht. In einem objektorientierten System ist das Aufrufen einer Methode so ziemlich das einzige, was Sie tun können. Tatsächlich ist das mehr oder weniger das Definition von "objektorientiert". Wenn ein Konstruktor keine Methoden aufrufen kann, was kann dann?

1
Jörg W Mittag

In O.O.P. Theorie sollte es keine Rolle spielen, aber in der Praxis jede O.O.P.-Programmiersprache behandelt Konstruktoren unterschiedlich. Ich benutze nicht sehr oft statische Methoden.

Wenn ich in C++ & Delphi einigen Eigenschaften ("Feldelementen") Anfangswerte geben musste und der Code sehr erweitert ist, füge ich einige sekundäre Methoden als Erweiterung der Konstruktoren hinzu.

Und nenne keine anderen Methoden, die komplexere Dinge tun.

Bei den Eigenschaften "Getter" und "Setter" verwende ich normalerweise private/geschützte Variablen, um ihren Status zu speichern, sowie die Methoden "Getter" und "Setter".

Im Konstruktor ordne ich den Eigenschaftsstatusfeldern "Standard" -Werte zu, OHNE die "Accessoren" aufrufen.

0
umlcat