Ich habe diese API-Funktion:
public ResultEnum DoSomeAction(string a, string b, DateTime c, OtherEnum d,
string e, string f, out Guid code)
Ich mag es nicht Weil die Parameterreihenfolge unnötig wichtig wird. Es wird schwieriger, neue Felder hinzuzufügen. Es ist schwieriger zu sehen, was herumgereicht wird. Es ist schwieriger, die Methode in kleinere Teile umzugestalten, da dadurch ein weiterer Aufwand für die Übergabe aller Parameter in Unterfunktionen entsteht. Code ist schwerer zu lesen.
Ich hatte die naheliegendste Idee: Lassen Sie ein Objekt die Daten kapseln und weitergeben, anstatt jeden Parameter einzeln zu übergeben. Folgendes habe ich mir ausgedacht:
public class DoSomeActionParameters
{
public string A;
public string B;
public DateTime C;
public OtherEnum D;
public string E;
public string F;
}
Das reduzierte meine API-Deklaration auf:
public ResultEnum DoSomeAction(DoSomeActionParameters parameters, out Guid code)
Nett. Sieht sehr unschuldig aus, aber wir haben tatsächlich eine große Veränderung eingeführt: Wir haben die Veränderlichkeit eingeführt. Denn was wir zuvor getan hatten, war tatsächlich, ein anonymes unveränderliches Objekt zu übergeben: Funktionsparameter auf dem Stapel. Jetzt haben wir eine neue Klasse erstellt, die sehr veränderlich ist. Wir haben die Möglichkeit geschaffen, den Status des Anrufers zu manipulieren . Das ist Scheiße. Jetzt möchte ich mein Objekt unveränderlich, was mache ich?
public class DoSomeActionParameters
{
public string A { get; private set; }
public string B { get; private set; }
public DateTime C { get; private set; }
public OtherEnum D { get; private set; }
public string E { get; private set; }
public string F { get; private set; }
public DoSomeActionParameters(string a, string b, DateTime c, OtherEnum d,
string e, string f)
{
this.A = a;
this.B = b;
// ... tears erased the text here
}
}
Wie Sie sehen, habe ich mein ursprüngliches Problem tatsächlich neu erstellt: zu viele Parameter. Es ist offensichtlich, dass dies nicht der richtige Weg ist. Was soll ich tun? Die letzte Möglichkeit, eine solche Unveränderlichkeit zu erreichen, besteht darin, eine "schreibgeschützte" Struktur wie diese zu verwenden:
public struct DoSomeActionParameters
{
public readonly string A;
public readonly string B;
public readonly DateTime C;
public readonly OtherEnum D;
public readonly string E;
public readonly string F;
}
Dadurch können wir Konstruktoren mit zu vielen Parametern vermeiden und Unveränderlichkeit erreichen. Eigentlich behebt es alle Probleme (Parameterreihenfolge usw.). Noch:
- Alle (einschließlich FXCop & Jon Skeet) sind sich einig, dass die Offenlegung öffentlicher Felder schlecht ist .
- Eric Lippert et al. Sagen, es sei eine Lüge , sich für die Unveränderlichkeit auf schreibgeschützte Felder zu verlassen .
In diesem Moment war ich verwirrt und beschloss, diese Frage zu schreiben: Was ist der einfachste Weg in C #, um das Problem "zu vieler Parameter" zu vermeiden, ohne die Veränderlichkeit einzuführen? Ist es möglich, eine schreibgeschützte Struktur für diesen Zweck zu verwenden und dennoch kein schlechtes API-Design zu haben?
ERKLÄRUNGEN:
- Bitte nehmen Sie an, dass kein Verstoß gegen das Prinzip der Einzelverantwortung vorliegt. In meinem ursprünglichen Fall schreibt die Funktion nur bestimmte Parameter in einen einzelnen DB-Datensatz.
- Ich suche keine spezifische Lösung für die gegebene Funktion. Ich suche einen allgemeinen Ansatz für solche Probleme. Ich bin speziell daran interessiert, das Problem "zu viele Parameter" zu lösen, ohne die Veränderlichkeit oder ein schreckliches Design einzuführen.
AKTUALISIEREN
Die hier gegebenen Antworten haben unterschiedliche Vor- und Nachteile. Deshalb möchte ich dies in ein Community-Wiki konvertieren. Ich denke, jede Antwort mit Codebeispiel und Vor- / Nachteilen wäre ein guter Leitfaden für ähnliche Probleme in der Zukunft. Ich versuche jetzt herauszufinden, wie es geht.
DoSomeActionParameters
es sich um ein Wegwerfobjekt handelt, das nach dem Methodenaufruf verworfen wird.Antworten:
Ein Stil, der in den Frameworks enthalten ist, ist normalerweise das Gruppieren verwandter Parameter in verwandte Klassen (aber wiederum problematisch mit der Veränderbarkeit):
quelle
Verwenden Sie eine Kombination aus Builder und domänenspezifischer API - Fluent Interface. Die API ist etwas ausführlicher, aber mit Intellisense ist sie sehr schnell zu tippen und leicht zu verstehen.
quelle
DoSomeAction
eine Methode dafür war.Was Sie dort haben, ist ein ziemlich sicherer Hinweis darauf, dass die betreffende Klasse gegen das Prinzip der Einzelverantwortung verstößt, weil sie zu viele Abhängigkeiten aufweist. Suchen Sie nach Möglichkeiten, diese Abhängigkeiten in Cluster von Fassadenabhängigkeiten umzugestalten .
quelle
Ändern Sie einfach Ihre Parameterdatenstruktur von a
class
nach astruct
und los geht's.Die Methode erhält nun eine eigene Kopie der Struktur. Änderungen an der Argumentvariablen können von der Methode nicht beobachtet werden, und Änderungen, die die Methode an der Variablen vornimmt, können vom Aufrufer nicht beobachtet werden. Die Isolierung wird ohne Unveränderlichkeit erreicht.
Vorteile:
Nachteile:
quelle
Wie wäre es, wenn Sie eine Builder-Klasse in Ihrer Datenklasse erstellen. Die Datenklasse hat alle Setter als privat und nur der Builder kann sie festlegen.
quelle
DoSomeActionParameters.Builder
Instanz verwendet werden, um genau eineDoSomeActionParameters
Instanz zu erstellen und zu konfigurieren . Nach dem AufrufBuild()
nachfolgender Änderungen an denBuilder
Eigenschaften von 'werden die Eigenschaften der ursprünglichenDoSomeActionParameters
Instanz weiter geändert , und nachfolgende Aufrufe vonBuild()
werden weiterhin dieselbeDoSomeActionParameters
Instanz zurückgeben. Es sollte wirklich so seinpublic DoSomeActionParameters Build() { var oldObj = obj; obj = new DoSomeActionParameters(); return oldObj; }
.Ich bin kein C # -Programmierer, aber ich glaube, C # unterstützt benannte Argumente: (F # funktioniert und C # ist weitgehend funktionskompatibel für solche Dinge) Es funktioniert: http://msdn.microsoft.com/en-us/library/dd264739 .aspx # Y342
Das Aufrufen Ihres Originalcodes wird also zu:
Dies nimmt keinen Platz mehr in Anspruch, da Ihre Objekterstellung alle Vorteile bietet, da Sie nicht geändert haben, was im unerliebten System überhaupt passiert. Sie müssen nicht einmal etwas neu codieren, um anzuzeigen, dass die Argumente benannt sind
Edit: hier ist ein Artikel, den ich darüber gefunden habe. http://www.globalnerdy.com/2009/03/12/default-and-named-parameters-in-c-40-sith-lord-in-training/ Ich sollte erwähnen, dass C # 4.0 benannte Argumente unterstützt, 3.0 nicht
quelle
Warum nicht einfach eine Schnittstelle erstellen, die Unveränderlichkeit erzwingt (dh nur Getter)?
Es ist im Wesentlichen Ihre erste Lösung, aber Sie erzwingen, dass die Funktion die Schnittstelle verwendet, um auf den Parameter zuzugreifen.
und die Funktionsdeklaration wird:
Vorteile:
struct
LösungNachteile:
DoSomeActionParameters
eine Klasse ist, der zugeordnet werden könnteIDoSomeActionParameters
quelle
IDoSomeActionParameters
und die darin enthaltenen Werte erfassen möchte, kann nicht wissen, ob das Halten des Verweises ausreicht oder ob er die Werte auf ein anderes Objekt kopieren muss. Lesbare Schnittstellen sind in einigen Kontexten nützlich, aber nicht, um Dinge unveränderlich zu machen.Ich weiß, dass dies eine alte Frage ist, aber ich dachte, ich würde mich meinem Vorschlag anschließen, da ich nur das gleiche Problem lösen musste. Zugegeben, mein Problem war etwas anders als das Ihre, da ich zusätzlich die Anforderung hatte, nicht zu wollen, dass Benutzer dieses Objekt selbst erstellen können (die gesamte Hydratation der Daten stammte aus der Datenbank, sodass ich alle Konstruktionen intern absperren konnte). Dies erlaubte mir, einen privaten Konstruktor und das folgende Muster zu verwenden;
quelle
Sie können einen Builder-ähnlichen Ansatz verwenden. Abhängig von der Komplexität Ihrer
DoSomeAction
Methode kann dies jedoch ein Touch-Schwergewicht sein. Etwas in diese Richtung:quelle
Zusätzlich zur Manji-Antwort möchten Sie möglicherweise auch eine Operation in mehrere kleinere aufteilen. Vergleichen Sie:
und
Für diejenigen, die POSIX nicht kennen, kann die Erstellung eines Kindes so einfach sein wie:
Jede Designauswahl stellt einen Kompromiss dar, welche Operationen sie ausführen kann.
PS. Ja - es ist ähnlich wie beim Builder - nur in umgekehrter Reihenfolge (dh auf der Angerufenen-Seite anstelle des Anrufers). In diesem speziellen Fall kann es besser sein als der Builder.
quelle
Hier ist etwas anderes als bei Mikeys, aber ich versuche, das Ganze so wenig wie möglich zu schreiben
Die DoSomeActionParameters sind unveränderlich und können nicht direkt erstellt werden, da ihr Standardkonstruktor privat ist
Der Initialisierer ist nicht unveränderlich, sondern nur ein Transport
Die Verwendung nutzt den Initialisierer auf dem Initialisierer (wenn Sie meine Drift erhalten) und ich kann Standardeinstellungen im Standardkonstruktor des Initialisierers haben
Die Parameter sind hier optional. Wenn Sie möchten, dass einige erforderlich sind, können Sie sie in den Initializer-Standardkonstruktor einfügen
Die Validierung kann in der Create-Methode erfolgen
So sieht es jetzt aus
Ich weiß, dass es immer noch ein kleiner Kooki ist, aber ich werde es trotzdem versuchen
Bearbeiten: Wenn Sie die Methode create in eine statische Datei im Parameterobjekt verschieben und einen Delegaten hinzufügen, der den Initialisierer übergibt, wird der Aufruf etwas kookieness
Der Anruf sieht jetzt so aus
quelle
Verwenden Sie die Struktur, haben Sie jedoch anstelle von öffentlichen Feldern öffentliche Eigenschaften:
Jon und FXCop werden zufrieden sein, weil Sie Eigenschaften und keine Felder verfügbar machen.
Eric wird zufrieden sein, da Sie mithilfe von Eigenschaften sicherstellen können, dass der Wert nur einmal festgelegt wird.
Ein halb unveränderliches Objekt (Wert kann gesetzt, aber nicht geändert werden). Funktioniert für Wert- und Referenztypen.
quelle
this
sind weitaus böser als öffentliche Felder. Ich bin mir nicht sicher, warum Sie denken, dass das Festlegen des Werts die Dinge "in Ordnung" macht. Wenn man mit einer Standardinstanz einer Struktur beginnt,this
bleiben die Probleme, die mit -mutierenden Eigenschaften verbunden sind, bestehen.Eine Variante von Samuels Antwort , die ich in meinem Projekt verwendet habe, als ich das gleiche Problem hatte:
Und zu verwenden:
In meinem Fall waren die Parameter absichtlich veränderbar, da die Setter-Methoden nicht alle möglichen Kombinationen zuließen und nur gemeinsame Kombinationen davon enthüllten. Das liegt daran, dass einige meiner Parameter ziemlich komplex waren und Schreibmethoden für alle möglichen Fälle schwierig und unnötig gewesen wären (verrückte Kombinationen werden selten verwendet).
quelle
DoMagic
hat in seinem Vertrag allerdings, dass es das Objekt nicht modifizieren wird. Aber ich denke, es schützt nicht vor versehentlichen Änderungen.