Wie ändert sich das Konzept einer Klasse, wenn Daten anstelle von Methodenparametern an den Konstruktor übergeben werden?

12

Nehmen wir an, wir machen einen Parser. Eine Implementierung könnte sein:

public sealed class Parser1
{
    public string Parse(string text)
    {
       ...
    }
}

Oder wir könnten den Text stattdessen an den Konstruktor übergeben:

public sealed class Parser2
{
    public Parser2(string text)
    {
       this.text = text;
    }

    public string Parse()
    {
       ...
    }
}

Die Verwendung ist einfach in beiden Fällen, aber was bedeutet es bedeuten Parametereingabe zu ermöglichen Parser1, im Vergleich zu den anderen? Welche Nachricht habe ich an einen anderen Programmierer gesendet, wenn dieser sich die API ansieht? Gibt es in bestimmten Fällen auch technische Vor- und Nachteile?

Eine andere Frage taucht auf, wenn mir klar wird, dass eine Schnittstelle in der zweiten Implementierung ziemlich bedeutungslos wäre:

public interface IParser
{
    string Parse();
}

... wo eine Schnittstelle auf der ersten zumindest einen Zweck erfüllen könnte. Bedeutet das etwas Besonderes, dass eine Klasse "interfaceable" ist oder nicht?

Ciscoheat
quelle
Dies ist das erste Mal, dass ich einige nützliche Fragen und Antworten dazu sehe, wie die Semantik von OO tatsächlich Absichten ausdrückt. Bis jetzt war alles für mich nur syntaktischer Zucker.

Antworten:

11

Semantisch gesehen sollten Sie in OOP dem Konstruktor nur eine Reihe von Parametern übergeben, die zum Erstellen der Klasse erforderlich sind. Wenn Sie eine Methode aufrufen, sollten Sie ihr nur die Parameter übergeben, die sie zum Ausführen ihrer Geschäftslogik benötigt.

Die Parameter, die Sie im Konstruktor übergeben müssen, sind Parameter, die keinen sinnvollen Standardwert haben. Wenn Ihre Klasse unveränderlich ist (oder tatsächlich a struct), müssen alle nicht standardmäßigen Eigenschaften übergeben werden.

In Bezug auf Ihre beiden Beispiele:

  • Wenn Sie textan den Konstruktor übergeben, weist dies darauf hin, dass die Parser2Klasse speziell zum Parsen dieser Textinstanz zu einem späteren Zeitpunkt erstellt wird. Es wird ein bestimmter Parser sein. Dies ist normalerweise der Fall, wenn das Erstellen der Klasse sehr teuer oder subtil ist. Möglicherweise wird ein RegEx im Konstruktor kompiliert. Sobald Sie eine Instanz besitzen, können Sie sie wiederverwenden, ohne die Kosten für das Kompilieren bezahlen zu müssen. Ein anderes Beispiel ist die Initialisierung des PRNG - es ist besser, wenn es selten durchgeführt wird.
  • Wenn Sie textan die Methode übergeben, werden Signale signalisiert, Parser1die zum Aufrufen verschiedener Texte durch Aufrufe wiederverwendet werden können.
Sklivvz
quelle
3
Ich würde hinzufügen, dass es ein schwaches Signal gibt, dass Parser1 den Status beibehält - dh, dass eine bestimmte Textzeichenfolge unterschiedliche Ergebnisse liefern kann, je nachdem, was zuvor für eine tat-Instanz durchgeführt wurde. Dies ist nicht unbedingt so, aber es kann sein.
jmoreno
8

Erinnern wir uns, was es bedeutet, eine Variable als Konstruktorparameter zu übergeben: Sie initialisieren ein Objekt, um seine Instanzvariablen in Methoden des Objekts zu verwenden. Der Punkt ist, dass Sie es wahrscheinlich in mehr als einer Methode verwenden möchten, da Sie einen hohen Zusammenhalt in Ihrer Klasse haben möchten .

Wenn Sie einen Parameter direkt an eine Methode übergeben, senden Sie in gewisser Weise eine Nachricht an ein Objekt und erhalten wahrscheinlich eine Antwort. Auf diese Weise möchte der Kunde, dass das Objekt eine Dienstleistung für ihn erbringt.

Zusammenfassend sind dies also zwei sehr unterschiedliche Methoden zum Übergeben von Parametern. Sie sollten entscheiden, ob Ihr Objekt entweder einen Dienst bereitstellen oder eine bestimmte Funktionalität bereitstellen soll, während Sie einige Informationen intern verwalten.

McMannus
quelle
+1. Kohäsion ist die Art und Weise, wie ich entscheide, ob sie zur Methode oder zum Konstruktor gehört.
Jhewlett
4

Die Verwendung ist in beiden Fällen einfach, aber was bedeutet es, die Parametereingabe in Parser1 im Vergleich zu den anderen zu aktivieren?

Es ist eine grundlegende Designänderung. Und Design sollte Absicht und Bedeutung vermitteln. Benötigen Sie separate Objekte für jede Zeichenfolge, die Sie analysieren möchten? Mit anderen Worten, warum benötigen wir eine Parserinstanz mit stringX und eine andere Instanz mit stringY? Was ist mit dem Parsen (ing) und der gegebenen Zeichenfolge, dass die beiden zusammen leben und sterben müssen? Unter der Annahme, dass sich die "zugrunde liegende [Parsing] -Implementierung" (wie Robert Harvey sagt) nicht ändert, scheint es keinen Sinn zu machen. Und selbst dann ist es meiner Meinung nach fraglich.

Wie ändert sich das Konzept einer Klasse, wenn Daten anstelle von Methodenparametern an den Konstruktor übergeben werden?

Konstruktorparameter sagen mir, dass diese Dinge für ein Objekt erforderlich sind. Der richtige Zustand ist ohne sie nicht garantiert. Außerdem weiß ich, wie / warum sich ein Parser grundlegend von einem anderen unterscheidet.

Konstruktorparameter verhindern, dass ich zu viel über die Verwendung der Klasse wissen muss. Wenn ich stattdessen bestimmte Eigenschaften festlegen soll - woher weiß ich das? Eine ganze Dose Würmer öffnet sich. Welche Eigenschaften? In welcher Reihenfolge? Bevor ich welche Methoden verwende? und so weiter.

Eine andere Frage taucht auf, wenn mir klar wird, dass eine Schnittstelle in der zweiten Implementierung ziemlich bedeutungslos wäre:

Eine Schnittstelle wie in der API sind die Methoden und Eigenschaften, die dem Clientcode ausgesetzt sind. Lassen Sie sich nicht public interface { ... }ausschließlich einwickeln . Die Bedeutung der Schnittstelle liegt also im Entweder-Oder-Konstruktor-gegen-Methodenparameter-Dilemma, NICHT im public interface Iparservspublic sealed class Parser

Die sealedKlasse ist seltsam. Wenn ich über verschiedene Parser-Implementierungen nachdenke - Sie haben "Iparser" erwähnt -, ist die Vererbung mein erster Gedanke. Es ist nur eine natürliche konzeptionelle Erweiterung in meinem Denken. IE alle ParserXs sind grundsätzlich Parsers. Wie soll ich es sonst sagen? ... Ein deutscher Schäferhund ist ein Hund (Erbschaft), aber ich kann meinen Papagei zum Bellen ausbilden (benimm dich wie ein Hund - "Schnittstelle"); aber Polly ist kein Hund, der nur so tut, als hätte er eine Untergruppe von Hunden gelernt. Abstrakte oder andere Klassen dienen perfekt als Schnittstellen .

Radarbob
quelle
Wenn es wie ein Parser läuft und wie ein Parser spricht, ist es ... eine Ente!
2

Die zweite Version der Klasse kann unveränderlich gemacht werden.

Die Schnittstelle kann weiterhin verwendet werden, um die zugrunde liegende Implementierung auszutauschen.

Robert Harvey
quelle
Kann es nicht auch in der ersten Version unveränderlich gemacht werden, indem Daten innerhalb der Klasse auf funktionale Weise weitergegeben werden?
Ciscoheat
2
Absolut. Das allgemeine Muster der Unveränderlichkeit besteht jedoch darin, die Klassenmitglieder mithilfe des Konstruktors festzulegen und schreibgeschützte Eigenschaften zu haben. Für die funktionale Programmierung benötigen Sie nicht einmal eine Klasse.
Robert Harvey
Die Scylla und Charybdis: Wähle ich OO oder unveränderliche Daten? Ich habe es noch nie so gehört.
2

Parser1

Das Erstellen mit einem Standardkonstruktor und das Übergeben von Eingabetext an eine Methode impliziert, dass Parser1 wiederverwendbar ist.

Parser2

Wenn Sie den Eingabetext an den Konstruktor übergeben, muss für jede Eingabezeichenfolge ein neuer Parser2 erstellt werden.

Mike Partridge
quelle
Klar, wenn wir es auf die nächste Ebene bringen, was ist dann über ein wiederverwendbares Objekt im Vergleich zu einem nicht wiederverwendbaren Objekt zu sagen?
Ciscoheat
Mehr würde ich nicht annehmen, stattdessen auf Dokumentation zurückgreifen.
Mike Partridge
Ein "wiederverwendbares Objekt", bei dem sich seine Identität ändert, ist keine Folge. Und mit verwalteten Frameworks, die bedeuten, dass Sie nicht einmal Dinge wegwerfen, einfach überschreiben oder außer Reichweite bringen müssen, ist dies sicherlich unnötig. Konstruiere weg!