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?
object-oriented
interfaces
methods
construction
Ciscoheat
quelle
quelle
Antworten:
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:
text
an den Konstruktor übergeben, weist dies darauf hin, dass dieParser2
Klasse 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.text
an die Methode übergeben, werden Signale signalisiert,Parser1
die zum Aufrufen verschiedener Texte durch Aufrufe wiederverwendet werden können.quelle
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.
quelle
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.
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 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 impublic interface Iparser
vspublic sealed class Parser
Die
sealed
Klasse 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 alleParserX
s sind grundsätzlichParser
s. 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 .quelle
Die zweite Version der Klasse kann unveränderlich gemacht werden.
Die Schnittstelle kann weiterhin verwendet werden, um die zugrunde liegende Implementierung auszutauschen.
quelle
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.
quelle