Wir führen viele Unit-Tests und Refactoring-Vorgänge für unsere Geschäftsobjekte durch, und ich habe anscheinend ganz andere Meinungen zum Klassendesign als andere Kollegen.
Eine Beispielklasse, von der ich kein Fan bin:
public class Foo
{
private string field1;
private string field2;
private string field3;
private string field4;
private string field5;
public Foo() { }
public Foo(string in1, string in2)
{
field1 = in1;
field2 = in2;
}
public Foo(string in1, string in2, string in3, string in4)
{
field1 = in1;
field2 = in2;
field3 = in3;
}
public Prop1
{ get { return field1; } }
{ set { field1 = value; } }
public Prop2
{ get { return field2; } }
{ set { field2 = value; } }
public Prop3
{ get { return field3; } }
{ set { field3 = value; } }
public Prop4
{ get { return field4; } }
{ set { field4 = value; } }
public Prop5
{ get { return field5; } }
{ set { field5 = value; } }
}
In der "echten" Klasse sind sie nicht alle Zeichenfolgen, aber in einigen Fällen haben wir 30 Hintergrundfelder für vollständig öffentliche Eigenschaften.
Ich hasse diese Klasse und ich weiß nicht, ob ich nur wählerisch bin. Ein paar bemerkenswerte Dinge:
- Private Sicherungsfelder ohne Logik in den Eigenschaften scheinen unnötig und blähen die Klasse auf
- Mehrere Konstruktoren (etwas in Ordnung), aber gekoppelt mit
- Alle Immobilien haben einen öffentlichen Setter, ich bin kein Fan.
- Möglicherweise wird aufgrund des leeren Konstruktors keinen Eigenschaften ein Wert zugewiesen. Wenn ein Aufrufer dies nicht weiß, kann dies möglicherweise zu unerwünschten und schwer zu testenden Verhaltensweisen führen.
- Es sind zu viele Eigenschaften! (im Fall 30)
Ich finde es viel schwieriger , als Implementierer wirklich zu wissen, in welchem Zustand sich das Objekt Foo
zu einem bestimmten Zeitpunkt befindet. Das Argument lautete: "Wir haben möglicherweise nicht die erforderlichen Informationen, um sie Prop5
zum Zeitpunkt der Objektkonstruktion festzulegen. Ok, ich denke, ich kann das verstehen, aber wenn dies der Fall ist, machen Sie nur Prop5
Setter öffentlich, nicht immer bis zu 30 Eigenschaften in einer Klasse."
Bin ich nur wählerisch und / oder verrückt danach, eine Klasse zu wollen, die "einfach zu bedienen" ist, anstatt "einfach zu schreiben (alles öffentlich)"? Klassen wie die oben genannten schreien mir zu, ich weiß nicht, wie das verwendet werden soll, also werde ich einfach alles für alle Fälle öffentlich machen .
Wenn ich nicht besonders wählerisch bin, was sind gute Argumente, um diese Art des Denkens zu bekämpfen? Ich bin nicht sehr gut darin, Argumente zu artikulieren, da ich sehr frustriert bin, wenn ich versuche, meinen Standpunkt zu vermitteln (natürlich nicht absichtlich).
quelle
get
oderset
:-)Antworten:
Vollständig öffentliche Klassen haben eine Rechtfertigung für bestimmte Situationen sowie für die anderen extremen Klassen mit nur einer öffentlichen Methode (und wahrscheinlich vielen privaten Methoden). Und Klassen mit einigen öffentlichen, einigen privaten Methoden.
Es hängt alles von der Art der Abstraktion ab, die Sie mit ihnen modellieren werden, welche Ebenen Sie in Ihrem System haben, welchen Grad an Kapselung Sie in den verschiedenen Ebenen benötigen und (natürlich) welche Denkschule der Autor der Klasse hat von. Sie finden alle diese Typen im SOLID-Code.
Es gibt ganze Bücher darüber, wann welche Art von Design bevorzugt werden soll, daher werde ich hier keine Regeln dafür auflisten, der Platz in diesem Abschnitt würde nicht ausreichen. Wenn Sie jedoch ein Beispiel aus der Praxis für eine Abstraktion haben, die Sie gerne mit einer Klasse modellieren, wird Ihnen die Community hier sicherlich gerne dabei helfen, das Design zu verbessern.
Um Ihre anderen Punkte anzusprechen:
Also statt
schreiben
oder
"Mehrere Konstruktoren": Das ist an sich kein Problem. Es tritt ein Problem auf, wenn dort unnötige Codeduplizierungen vorhanden sind, wie in Ihrem Beispiel gezeigt, oder wenn die aufrufende Hierarchie verschlungen ist. Dies kann leicht gelöst werden, indem gemeinsame Teile in eine separate Funktion umgestaltet werden und die Konstruktorkette unidirektional organisiert wird
"Möglicherweise wird aufgrund des leeren Konstruktors keinen Eigenschaften ein Wert zugewiesen": In C # hat jeder Datentyp einen klar definierten Standardwert. Wenn Eigenschaften in einem Konstruktor nicht explizit initialisiert werden, wird ihnen dieser Standardwert zugewiesen. Wenn dies absichtlich verwendet wird , ist es vollkommen in Ordnung - ein leerer Konstruktor könnte also in Ordnung sein, wenn der Autor weiß, was er tut.
"Es sind zu viele Immobilien! (Im Fall 30)": Ja, wenn Sie die Freiheit haben, eine solche Klasse auf der grünen Wiese zu entwerfen, sind 30 zu viele, stimme ich zu. Allerdings hat nicht jeder von uns diesen Luxus (haben Sie nicht im Kommentar unten geschrieben, dass es sich um ein Legacy-System handelt?). Manchmal müssen Sie Datensätze aus einer vorhandenen Datenbank oder Datei oder Daten aus einer Drittanbieter-API Ihrem System zuordnen. In diesen Fällen könnten 30 Attribute etwas sein, mit dem man leben muss.
quelle
null
ist, nicht wahr? Wenn also eine der Eigenschaften, die tatsächlich in a verwendet wird.ToUpper()
, aber niemals ein Wert dafür festgelegt wird, eine Laufzeitausnahme auslöst. Dies ist ein weiteres gutes Beispiel dafür, warum die erforderlichen Daten für die Klasse sollten haben bei der Objektkonstruktion festgelegt werden. Überlassen Sie es nicht nur dem Benutzer. DANKEWenn Sie sagen, dass "Private Backing-Felder ohne Logik in den Eigenschaften unnötig erscheinen und die Klasse aufblähen", haben Sie bereits ein anständiges Argument.
Es scheint nicht, dass hier mehrere Konstruktoren das Problem sind.
Wenn Sie alle Eigenschaften als öffentlich haben möchten ... Wenn Sie jemals den Zugriff auf all diese Eigenschaften zwischen Threads synchronisieren wollten, würde es Ihnen schwer fallen, Synchronisationscode überall dort zu schreiben, wo sie sich befinden benutzt. Wenn jedoch alle Eigenschaften von Gettern / Setzern eingeschlossen wären, könnten Sie die Synchronisation problemlos in die Klasse einbauen.
Ich denke, wenn Sie "schwer zu testendes Verhalten" sagen, spricht das Argument für sich. Ihr Test muss möglicherweise überprüfen, ob er nicht null oder ähnliches ist (jeder einzelne Ort, an dem die Eigenschaft verwendet wird). Ich weiß es nicht wirklich, weil ich nicht weiß, wie Ihr Test / Ihre Anwendung aussieht.
Sie haben viel zu viele Eigenschaften, könnten Sie versuchen, die Vererbung zu verwenden (wenn die Eigenschaften ähnlich genug sind) und dann eine Liste / ein Array mit Generika haben? Dann könnten Sie Getter / Setter schreiben, um auf die Informationen zuzugreifen und die Interaktion mit allen Eigenschaften zu vereinfachen.
quelle
Wie Sie mir sagten,
Foo
ist ein Geschäftsobjekt. Es sollte ein gewisses Verhalten in Methoden entsprechend Ihrer Domain kapseln und seine Daten müssen so gekapselt wie möglich bleiben.In dem gezeigten Beispiel
Foo
ähnelt es eher einem DTO und verstößt gegen alle OOP-Prinzipien (siehe Anämisches Domänenmodell )!Als Verbesserungen würde ich vorschlagen:
Dies könnte eine potenzielle überarbeitete Klasse mit folgenden Bemerkungen sein:
quelle
"Wir können diese überarbeiteten Methoden jetzt einem Komponententest unterziehen. Da der Client aus den Gründen x, y, z nicht testbar ist.
Soweit eines der oben genannten Argumente insgesamt vorgebracht werden kann:
quelle