OOP-Codierungsstil: Alles im Konstruktor initialisieren?

14

Ich betrachte mich immer noch als Programmiererlehrling und bin immer auf der Suche nach einem "besseren" Weg für typisches Programmieren. Heute hat mein Kollege argumentiert, dass mein Codierungsstil unnötige Arbeit leistet, und ich möchte Meinungen von anderen hören. Wenn ich eine Klasse in OOP-Sprache entwerfe (normalerweise C ++ oder Python), teile ich die Initialisierung normalerweise in zwei verschiedene Teile auf:

class MyClass1 {
public:
    Myclass1(type1 arg1, type2 arg2, type3 arg3);
    initMyClass1();
private:
    type1 param1;
    type2 param2;
    type3 param3;
    type4 anotherParam1;
};

// Only the direct assignments from the input arguments are done in the constructor
MyClass1::myClass1(type1 arg1, type2 arg2, type3 arg3)
    : param1(arg1)
    , param2(arg2)
    , param3(arg3)
    {}

// Any other procedure is done in a separate initialization function 
MyClass1::initMyClass1() {
    // Validate input arguments before calculations
    if (checkInputs()) {
    // Do some calculations here to figure out the value of anotherParam1
        anotherParam1 = someCalculation();
    } else {
        printf("Something went wrong!\n");
        ASSERT(FALSE)
    }
}

(oder Python-Äquivalent)

class MyClass1:

    def __init__(self, arg1, arg2, arg3):
        self.arg1 = arg1
        self.arg2 = arg2
        self.arg3 = arg3
        #optional
        self.anotherParam1 = None

    def initMyClass1():
        if checkInputs():
            anotherParam1 = someCalculation()
        else:
            raise "Something went wrong!"

Wie ist Ihre Meinung zu diesem Ansatz? Sollte ich den Initialisierungsprozess nicht aufteilen? Die Frage ist nicht nur auf C ++ und Python beschränkt, sondern es werden auch Antworten für andere Sprachen geschätzt.

Caladbolgll
quelle
1
Und für Python: stackoverflow.com/questions/20661448/…
Doc Brown
Warum machst du das normalerweise? Gewohnheit? Wurde Ihnen jemals ein Grund gegeben, dies zu tun?
JeffO
@ JeffO Ich habe diese Angewohnheit, als ich daran arbeitete, GUI mit MFC-Bibliothek zu machen. Die meisten UI-bezogenen Klassen wie CApp, CWindow, CDlg usw. verfügen über OnInit () -Funktionen, die Sie überschreiben können und die auf die entsprechenden Nachrichten reagieren.
Caladbolgll

Antworten:

28

Obwohl es manchmal problematisch ist, hat das Initialisieren von allem im Konstruktor viele Vorteile:

  1. Wenn ein Fehler auftritt, geschieht dies so schnell wie möglich und ist am einfachsten zu diagnostizieren. Wenn beispielsweise null ein ungültiger Argumentwert ist, testen Sie den Konstruktor und führen Sie einen Fehler aus.
  2. Das Objekt befindet sich immer in einem gültigen Zustand. Ein Mitarbeiter kann keinen Fehler machen und vergessen anzurufen, initMyClass1()weil er nicht da ist . "Die billigsten, schnellsten und zuverlässigsten Komponenten sind diejenigen, die es nicht gibt."
  3. Wenn es Sinn macht, kann das Objekt unveränderlich gemacht werden, was viele Vorteile hat.
user949300
quelle
2

Denken Sie an die Abstraktion, die Sie Ihren Benutzern bereitstellen.

Warum etwas aufteilen, was auf einmal gemacht werden kann?

Die zusätzliche Initialisierung ist nur etwas Besonderes für Programmierer, die Ihre API verwenden, um sich daran zu erinnern, und bietet mehr Möglichkeiten, um Fehler zu machen, wenn sie es nicht richtig machen, aber welchen Wert haben sie für diese zusätzliche Belastung?

Sie möchten ganz einfach, benutzerfreundlich und mit schwer zu begehenden Abstraktionen ausstatten. Das Programmieren ist schon schwer genug, ohne dass man sich unnötige Dinge merken muss. Sie möchten, dass Ihre API-Benutzer (auch wenn Sie nur Ihre eigene API verwenden) in die Erfolgsgrube geraten .

Erik Eidt
quelle
1

Initialisieren Sie alles außer Big Data. Statische Analysewerkzeuge kennzeichnen Felder, die im Konstruktor nicht initialisiert wurden. Am produktivsten und sichersten ist es jedoch, alle Mitgliedsvariablen mit Standardkonstruktoren zu versehen und nur diejenigen explizit zu initialisieren, die eine nicht standardmäßige Initialisierung erfordern.

zzz777
quelle
0

Es gibt Fälle, in denen das Objekt viele Initialisierungen aufweist, die in zwei Kategorien unterteilt werden können:

  1. Attribute, die entweder unveränderlich sind oder nicht zurückgesetzt werden müssen.

  2. Attribute, die möglicherweise auf die ursprünglichen Werte (oder Werte mit Vorlagen) zurückgesetzt werden müssen, basierend auf einer bestimmten Bedingung, nachdem sie ihren Job ausgeführt haben, eine Art Soft-Reset. zB Verbindungen in einem Verbindungspool.

Hier kann der zweite Teil der Initialisierung, der in einer separaten Funktion namens InitialiseObject () gespeichert ist, im ctor aufgerufen werden.

Dieselbe Funktion kann später aufgerufen werden, wenn ein Soft-Reset erforderlich ist, ohne dass das Objekt verworfen und neu erstellt werden muss.

Ramakant
quelle
0

Wie andere bereits gesagt haben, ist es im Allgemeinen eine gute Idee, im Konstruktor zu initialisieren.

Es gibt jedoch Gründe, die in bestimmten Fällen möglicherweise nicht zutreffen.

Fehlerbehandlung

In vielen Sprachen besteht die einzige Möglichkeit, einen Fehler in einem Konstruktor anzuzeigen, darin, eine Ausnahme auszulösen.

Wenn Ihre Initialisierung eine vernünftige Chance hat, einen Fehler auszulösen, z. B. wenn es sich um eine E / A-Operation handelt oder wenn die Parameter vom Benutzer eingegeben werden, können Sie nur eine Ausnahme auslösen. In einigen Fällen ist dies möglicherweise nicht das, was Sie möchten, und es kann sinnvoller sein, den fehleranfälligen Code auf eine separate Initialisierungsfunktion aufzuteilen.

Das wahrscheinlich häufigste Beispiel hierfür ist C ++, wenn der Projekt- / Organisationsstandard darin besteht, Ausnahmen auszuschalten.

Zustandsmaschine

Dies ist der Fall, wenn Sie ein Objekt mit expliziten Statusübergängen modellieren. Zum Beispiel eine Datei oder ein Socket, der geöffnet und geschlossen werden kann.

In diesem Fall ist es üblich, dass die Objektkonstruktion (und -löschung) nur speicherorientierte Attribute (Dateiname, Port usw.) behandelt. Es wird dann Funktionen zum gezielten Verwalten der Zustandsübergänge geben, z. B. Öffnen, Schließen, die effektiv Initialisierungs- und Abreißfunktionen sind.

Die Vorteile liegen in der Fehlerbehandlung, wie oben, es kann jedoch auch ein Fall vorliegen, bei dem die Konstruktion von der Initialisierung getrennt wird (beispielsweise wenn Sie einen Vektor von Dateien erstellen und diese asynchron öffnen).

Der Nachteil ist, wie andere gesagt haben, dass Sie jetzt die Last der Zustandsverwaltung auf den Benutzer Ihrer Klassen legen. Wenn Sie mit nur einer Konstruktion auskommen könnten, könnten Sie beispielsweise RAII verwenden, um dies automatisch zu tun.

Alex
quelle