Können wir ohne Bauherren leben?

25

Angenommen, aus irgendeinem Grund werden alle Objekte auf diese Weise erstellt: $ obj = CLASS :: getInstance (). Dann injizieren wir Abhängigkeiten mit Setzern und starten die Initialisierung mit $ obj-> initInstance (); Gibt es echte Probleme oder Situationen, die nicht gelöst werden können, wenn wir überhaupt keine Konstruktoren verwenden?

Ps der Grund für das Erstellen von Objekten auf diese Weise ist, dass wir die Klasse in getInstance () nach bestimmten Regeln ersetzen können.

Ich arbeite in PHP, wenn das wichtig ist

Axel Foley
quelle
welche programmiersprache?
gnat
2
PHP (warum ist das wichtig?)
Axel Foley
8
Sieht so aus, als ob das, was Sie tun möchten, mit dem Factory-Muster erreicht werden kann.
SuperM
1
Nur als Hinweis: Das erwähnte Werkspatern @superM ist eine andere Möglichkeit zur Implementierung von Inversion of Control (Dependency Injection).
Joachim Sauer
Ist das nicht ungefähr das, was Javascript mit Objekten macht?
Thijser

Antworten:

44

Ich würde sagen, dass dies Ihren Designraum stark behindert.

Konstruktoren sind ein großartiger Ort, um übergebene Parameter zu initialisieren und zu validieren. Wenn Sie sie dafür nicht mehr verwenden können, wird die Initialisierung der Statusverarbeitung (oder das Verweigern des Konstruktors "defekter" Objekte) viel schwieriger und teilweise unmöglich.

Wenn beispielsweise jedes FooObjekt ein benötigt, Frobnicatorprüft es möglicherweise in seinem Konstruktor, ob das Objekt Frobnicatornicht null ist. Wenn Sie den Konstruktor wegnehmen, wird es schwieriger zu überprüfen. Prüfen Sie an jedem Punkt, wo es verwendet werden würde? In einer init()Methode (effektiv Externalisierung der Konstruktormethode)? Überprüfen Sie es nie und hoffen Sie auf das Beste?

Während Sie wahrscheinlich immer noch alles implementieren könnten (schließlich sind Sie immer noch vollständig), werden einige Dinge viel schwieriger zu tun sein.

Persönlich würde ich vorschlagen, sich mit Abhängigkeitsinjektion / Inversion der Kontrolle zu befassen . Diese Techniken ermöglichen auch das Wechseln konkreter Implementierungsklassen, verhindern jedoch nicht das Schreiben / Verwenden von Konstruktoren.

Joachim Sauer
quelle
Was meinten Sie mit "oder klar leugnen Konstruktor von" kaputten "Objekten"?
Geek
2
@Geek: Ein Konstruktor kann sein Argument überprüfen und entscheiden, ob dies zu einem Arbeitsobjekt führen würde (wenn Ihr Objekt beispielsweise ein benötigt HttpClient, prüft er, ob dieser Parameter nicht null ist). Und wenn diese Bedingungen nicht erfüllt sind, kann dies eine Ausnahme auslösen. Das ist mit dem Konstrukt-und-Set-Werte-Ansatz nicht wirklich möglich.
Joachim Sauer
1
Ich denke, OP hat nur die Externalisierung des Konstruktors im Inneren beschrieben init(), was durchaus möglich ist - obwohl dies nur einen größeren Wartungsaufwand bedeutet.
Peter
26

2 Vorteile für Konstrukteure:

Konstruktoren ermöglichen die atomare Ausführung der Konstruktionsschritte eines Objekts.

Ich könnte einen Konstruktor umgehen und für alles Setter verwenden, aber was ist mit den obligatorischen Eigenschaften, wie Joachim Sauer vorgeschlagen hat? Mit einem Konstruktor besitzt ein Objekt eine eigene Konstruktionslogik, um sicherzustellen, dass es keine ungültigen Instanzen einer solchen Klasse gibt .

Wenn beim Erstellen einer Instanz von Foo3 Eigenschaften festgelegt werden müssen, kann der Konstruktor einen Verweis auf alle 3 nehmen, diese validieren und eine Ausnahme auslösen, wenn sie ungültig sind.

Verkapselung

Wenn Sie sich ausschließlich auf Setter verlassen, liegt die Last beim Verbraucher eines Objekts, um es richtig zu bauen. Es kann verschiedene Kombinationen von Eigenschaften geben, die gültig sind.

Beispielsweise Foobenötigt jede Instanz entweder eine Instanz von Barals Eigenschaft baroder eine Instanz von BarFinderals Eigenschaft barFinder. Es kann eines von beiden verwenden. Sie können für jeden gültigen Parametersatz einen Konstruktor erstellen und die Konventionen auf diese Weise durchsetzen.

Die Logik und Semantik für die Objekte befindet sich im Objekt selbst. Es ist eine gute Verkapselung.

Brandon
quelle
15

Ja, Sie können ohne Konstrukteure leben.

Sicher, es kann vorkommen, dass Sie eine Menge doppelten Kesselschildcodes haben. Und wenn Ihre Anwendung von beliebiger Größe ist, werden Sie wahrscheinlich viel Zeit damit verbringen, die Ursache eines Problems zu lokalisieren, wenn dieser Code nicht in der gesamten Anwendung konsistent verwendet wird.

Aber nein, Sie brauchen Ihre eigenen Konstrukteure nicht unbedingt. Natürlich brauchen Sie auch keine Klassen und Objekte.

Wenn Sie nun eine Art Factory-Muster für die Objekterstellung verwenden möchten, schließt sich dies nicht gegenseitig aus, wenn Sie beim Initialisieren Ihrer Objekte Konstruktoren verwenden.

GroßmeisterB
quelle
Absolut. Man kann sogar ohne Klassen und Objekte leben.
JensG
5
Alle grüßen den mächtigen Assembler!
Davor Ždralo
9

Der Vorteil der Verwendung von Konstruktoren besteht darin, dass sie es einfacher machen, sicherzustellen, dass Sie niemals ein ungültiges Objekt haben.

Ein Konstruktor gibt Ihnen die Möglichkeit, alle Mitgliedsvariablen des Objekts in einen gültigen Zustand zu versetzen. Wenn Sie dann sicherstellen, dass keine Mutator-Methode das Objekt in einen ungültigen Status ändern kann, wird es nie ein ungültiges Objekt geben, wodurch Sie vor vielen Fehlern bewahrt werden.

Wenn jedoch ein neues Objekt in einem ungültigen Zustand erstellt wird und Sie einige Setter aufrufen müssen, um es in einen gültigen Zustand zu versetzen, in dem es verwendet werden kann, riskieren Sie, dass ein Konsument der Klasse vergisst, diese Setter aufzurufen, oder sie falsch und falsch aufruft Sie erhalten ein ungültiges Objekt.

Eine Problemumgehung könnte darin bestehen, Objekte nur mit einer Factory-Methode zu erstellen, die jedes erstellte Objekt auf Gültigkeit überprüft, bevor es an den Aufrufer zurückgegeben wird.

Philipp
quelle
3

$ obj = CLASS :: getInstance (). Dann injizieren wir Abhängigkeiten mit Setzern und starten die Initialisierung mit $ obj-> initInstance ();

Ich denke, Sie machen das schwieriger, als es sein muss. Abhängigkeiten können problemlos über den Konstruktor eingefügt werden. Wenn Sie viele davon haben, verwenden Sie einfach eine wörterbuchähnliche Struktur, damit Sie angeben können, welche Sie verwenden möchten:

$obj = new CLASS(array(
    'Frobnicator' => (),
    'Foonicator' => (),
));

Und innerhalb des Konstruktors können Sie die Konsistenz folgendermaßen sicherstellen:

if (!array_key_exists('Frobnicator', $args)) {
    throw new Exception('Frobnicator required');
}
if (!array_key_exists('Foonicator', $args)) {
    $args['Foonicator'] = new DefaultFoonicator();
}

$args kann dann verwendet werden, um private Mitglieder nach Bedarf festzulegen.

Wenn dies vollständig innerhalb des Konstruktors geschieht, wird es niemals einen Zwischenzustand geben, in dem es $objexistiert, der jedoch nicht initialisiert wird, wie dies bei dem in der Frage beschriebenen System der Fall wäre. Es ist besser, solche Zwischenzustände zu vermeiden, da Sie nicht garantieren können, dass das Objekt immer korrekt verwendet wird.

Izkata
quelle
2

Ich habe tatsächlich über ähnliche Dinge nachgedacht.

Die Frage, die ich gestellt habe, lautete: "Was macht der Konstruktor und ist es möglich, es anders zu machen?" Ich bin zu folgenden Schlussfolgerungen gekommen:

  • Dadurch wird sichergestellt, dass einige Eigenschaften initialisiert werden. Indem Sie sie als Parameter akzeptieren und einstellen. Dies kann jedoch leicht vom Compiler erzwungen werden. Durch einfaches Annotieren der Felder oder Eigenschaften als "erforderlich" würde der Compiler während der Erstellung der Instanz prüfen, ob alles richtig eingestellt ist. Der Aufruf zum Erstellen der Instanz wäre wahrscheinlich derselbe, es gäbe einfach keine Konstruktormethode.

  • Stellt sicher, dass die Eigenschaften gültig sind. Dies könnte leicht erreicht werden, indem Bedingung geltend gemacht wird. Auch hier würden Sie die Eigenschaften nur mit den richtigen Bedingungen versehen. Einige Sprachen tun dies bereits.

  • Etwas komplexere Konstruktionslogik. Moderne Patterns empfehlen, dies nicht im Konstruktor zu tun, sondern spezialisierte Factory-Methoden oder -Klassen zu verwenden. Der Einsatz von Konstruktoren ist in diesem Fall also minimal.

Um Ihre Frage zu beantworten: Ja, ich glaube, es ist möglich. Aber es würde einige große Änderungen im Sprachdesign erfordern.

Und mir ist gerade aufgefallen, dass meine Antwort ziemlich OT ist.

Euphorisch
quelle
2

Ja, Sie können fast alles tun, ohne Konstruktoren zu verwenden. Dies ist jedoch ein klarer Vorteil objektorientierter Programmiersprachen.

In modernen Sprachen (ich werde hier auf C # eingehen, in dem ich programmiere) können Sie Codeteile einschränken, die nur in einem Konstruktor ausgeführt werden können. Dank dessen können Sie ungeschickte Fehler vermeiden. Eines davon ist readonly modifier:

public class A {
    readonly string rostring;

    public A(string arg) {
        rostring = arg;
    }

    public static A CreateInstance(string arg) {
        var result = new A();
        A.rostring = arg;  // < because of this the code won't compile!
        return result;
    }
}

Wie von Joachim Sauer zuvor empfohlen, anstatt FactoryDesignmuster zu verwenden, lesen Sie weiter Dependency Injection. Ich würde empfehlen, Dependency Injection in .NET von Mark Seemann zu lesen .

SOReader
quelle
1

Instanziieren Sie ein Objekt mit einem Typ, der von den Anforderungen abhängt, die absolut möglich sind. Es könnte das Objekt selbst sein, das globale Variablen des Systems verwendet, um einen bestimmten Typ zurückzugeben.

Eine Klasse im Code, die "All" sein kann, ist das Konzept des dynamischen Typs . Ich persönlich glaube, dass dieser Ansatz zu Inkonsistenzen in Ihrem Code führt, die Testkomplexe * erstellt und "die Zukunft wird ungewiss" in Bezug auf den Fluss der vorgeschlagenen Arbeit.

* Ich beziehe mich auf die Tatsache, dass die Tests erstens den Typ und zweitens das gewünschte Ergebnis berücksichtigen müssen. Dann erstellen Sie einen großen verschachtelten Test.

marcocs
quelle
1

Um einige der anderen Antworten auszugleichen, behaupten Sie:

Ein Konstruktor gibt Ihnen die Möglichkeit, alle Mitgliedsvariablen des Objekts in einen gültigen Zustand zu versetzen. Sie werden niemals ein ungültiges Objekt haben, was Sie vor vielen Fehlern bewahren wird.

und

Mit einem Konstruktor besitzt ein Objekt eine eigene Konstruktionslogik, um sicherzustellen, dass es keine ungültigen Instanzen einer solchen Klasse gibt.

Solche Aussagen implizieren manchmal die Annahme, dass:

Wenn eine Klasse über einen Konstruktor verfügt, der das Objekt zum Zeitpunkt seines Abschlusses in einen gültigen Zustand versetzt hat und keine der Klassenmethoden diesen Zustand ändert, um es ungültig zu machen, kann kein Code außerhalb der Klasse ein Objekt erkennen dieser Klasse in einem ungültigen Zustand.

Das ist aber nicht ganz richtig. Die meisten Sprachen haben keine Regel gegen einen Konstruktor, der this( selfoder wie auch immer die Sprache es nennt) an externen Code übergibt . Ein solcher Konstruktor hält sich vollständig an die oben angegebene Regel und riskiert dennoch, halbkonstruierte Objekte externem Code auszusetzen. Es ist ein kleiner Punkt, aber leicht zu übersehen.

Daniel Earwicker
quelle
0

Dies ist etwas anekdotisch, aber ich behalte mir normalerweise Konstruktoren für einen Zustand vor, der für die Vollständigkeit und Verwendbarkeit des Objekts wesentlich ist. Abgesehen von Setter-Injection sollte mein Objekt nach Ausführung des Konstruktors in der Lage sein, die dafür erforderlichen Aufgaben auszuführen.

Alles, was aufgeschoben werden kann, lasse ich aus dem Konstruktor heraus (Vorbereiten von Ausgabewerten usw.). Bei diesem Ansatz halte ich Konstruktoren für nichts anderes als die Abhängigkeitsinjektion für sinnvoll.

Dies hat auch den zusätzlichen Vorteil, dass Sie Ihren mentalen Entwurfsprozess fest verdrahten, um nichts vorzeitig zu tun. Sie werden keine Logik initialisieren oder ausführen, die möglicherweise nie verwendet wird, da Sie bestenfalls nur die Grundeinstellungen für die bevorstehende Arbeit vornehmen.

Omega
quelle