Beispielsweise hat eine Klasse normalerweise Klassenmitglieder und -methoden, z.
public class Cat{
private String name;
private int weight;
private Image image;
public void printInfo(){
System.out.println("Name:"+this.name+",weight:"+this.weight);
}
public void draw(){
//some draw code which uses this.image
}
}
Aber nachdem ich über das Prinzip der Einzelverantwortung und das Prinzip der offenen Schließung gelesen habe, ziehe ich es vor, eine Klasse nur mit statischen Methoden in DTO- und Hilfsklasse zu unterteilen, z.
public class CatData{
public String name;
public int weight;
public Image image;
}
public class CatMethods{
public static void printInfo(Cat cat){
System.out.println("Name:"+cat.name+",weight:"+cat.weight);
}
public static void draw(Cat cat){
//some draw code which uses cat.image
}
}
Ich denke, es passt zum Prinzip der Einzelverantwortung, da CatData jetzt nur noch Daten aufbewahrt und sich nicht um die Methoden kümmert (auch nicht um CatMethods). Und es passt auch zum Open-Closed-Prinzip, da durch Hinzufügen neuer Methoden die CatData-Klasse nicht geändert werden muss.
Meine Frage ist, ist es ein gutes oder ein Anti-Muster?
object-oriented
static-methods
dto
ocomfd
quelle
quelle
Antworten:
Sie haben zwei Extreme gezeigt ("alles Private und alle (möglicherweise nicht verwandten) Methoden in einem Objekt" vs. "Alles öffentlich und keine Methode innerhalb des Objekts"). IMHO gute OO-Modellierung ist keine von ihnen , der Sweet Spot ist irgendwo in der Mitte.
Ein Lackmustest, welche Methoden oder Logik zu einer Klasse gehören und was außerhalb gehört, besteht darin, die Abhängigkeiten zu untersuchen, die die Methoden einführen. Methoden, die keine zusätzlichen Abhängigkeiten einführen, sind in Ordnung, solange sie gut zur Abstraktion des angegebenen Objekts passen . Methoden, die zusätzliche externe Abhängigkeiten erfordern (wie eine Zeichnungsbibliothek oder eine E / A-Bibliothek), passen selten gut zusammen. Selbst wenn Sie die Abhängigkeiten mithilfe der Abhängigkeitsinjektion verschwinden lassen würden, würde ich mir zweimal überlegen, ob das Platzieren solcher Methoden in der Domänenklasse wirklich notwendig ist.
Sie sollten also weder jedes Mitglied öffentlich machen, noch Methoden für jede Operation für ein Objekt innerhalb der Klasse implementieren. Hier ist ein alternativer Vorschlag:
Jetzt
Cat
bietet das Objekt genügend Logik, um den umgebenden Code einfach zu implementierenprintInfo
unddraw
ohne alle Attribute öffentlich zugänglich zu machen. Der richtige Ort für diese beiden Methoden ist höchstwahrscheinlich keine GottklasseCatMethods
(daprintInfo
unddraw
höchstwahrscheinlich unterschiedliche Anliegen sind, daher halte ich es für sehr unwahrscheinlich, dass sie zur selben Klasse gehören).Ich kann mir eine
CatDrawingController
Implementierung vorstellendraw
(und möglicherweise die Abhängigkeitsinjektion zum Abrufen eines Canvas-Objekts verwenden). Ich kann mir auch eine andere Klasse vorstellen, die einige Konsolenausgaben implementiert und verwendetgetInfo
(daherprintInfo
kann dies in diesem Zusammenhang veraltet sein). Aber um vernünftige Entscheidungen zu treffen, muss man den Kontext kennen und wissen, wie dieCat
Klasse tatsächlich verwendet wird.Auf diese Weise habe ich Fowlers Kritiker des anämischen Domänenmodells interpretiert - für allgemein wiederverwendbare Logik (ohne externe Abhängigkeiten) sind die Domänenklassen selbst ein guter Ort, daher sollten sie dafür verwendet werden. Das heißt aber nicht, dort eine Logik zu implementieren, ganz im Gegenteil.
Beachten Sie auch, dass das obige Beispiel noch Raum für Entscheidungen über (Un-) Veränderlichkeit lässt. Wenn die
Cat
Klasse keine Setter verfügbar macht undImage
selbst unveränderlich ist, kann mit diesem EntwurfCat
unveränderlich gemacht werden (was beim DTO-Ansatz nicht der Fall ist). Wenn Sie jedoch der Meinung sind, dass Unveränderlichkeit für Ihren Fall nicht erforderlich oder nicht hilfreich ist, können Sie auch in diese Richtung gehen.quelle
getInfo()
könnte es für jemanden, der diese Frage stellt, irreführend sein. Es mischt subtil Präsentationsverantwortlichkeiten wie esprintInfo()
tut und besiegt den Zweck derDrawingController
KlasseDrawingController
. Ich stimme dem zugetInfo()
und binprintInfo()
schreckliche Namen. BetrachtentoString()
undprintTo(Stream stream)
Späte Antwort, aber ich kann nicht widerstehen.
In den meisten Fällen werden die meisten Regeln, die ohne nachzudenken angewendet werden, schrecklich schief gehen (einschließlich dieser).
Lassen Sie mich Ihnen eine Geschichte über die Geburt eines Objekts im Chaos eines richtigen, schnellen und schmutzigen Verfahrenscodes erzählen, der nicht beabsichtigt, sondern aus Verzweiflung entstanden ist.
Mein Praktikant und ich programmieren Paare, um schnell einen Wegwerfcode zum Abkratzen einer Webseite zu erstellen. Wir haben absolut keinen Grund zu der Annahme, dass dieser Code lange leben wird, also schlagen wir nur etwas heraus, das funktioniert. Wir nehmen die ganze Seite als Schnur und hacken das Zeug, das wir brauchen, auf die erstaunlich spröde Art und Weise, die Sie sich vorstellen können. Beurteile nicht. Es klappt.
Währenddessen habe ich einige statische Methoden zum Hacken erstellt. Mein Praktikant hat eine DTO-Klasse erstellt, die Ihrer sehr ähnlich war
CatData
.Als ich mir das DTO zum ersten Mal ansah, nervte es mich. Die jahrelangen Schäden, die Java meinem Gehirn zugefügt hat, haben mich auf den öffentlichen Feldern zurückgeworfen. Aber wir haben in C # gearbeitet. C # benötigt keine vorzeitigen Getter und Setter, um Ihr Recht zu bewahren, die Daten unveränderlich zu machen oder später zu kapseln. Ohne die Benutzeroberfläche zu ändern, können Sie sie jederzeit hinzufügen. Vielleicht nur, damit Sie einen Haltepunkt setzen können. Alles ohne Ihren Kunden etwas darüber zu erzählen. Ja C #. Boo Java.
Also hielt ich meine Zunge. Ich sah zu, wie er meine statischen Methoden verwendete, um dieses Ding zu initialisieren, bevor er es benutzte. Wir hatten ungefähr 14 davon. Es war hässlich, aber wir hatten keinen Grund, uns darum zu kümmern.
Dann brauchten wir es an anderen Orten. Wir wollten den Code kopieren und einfügen. 14 Initialisierungszeilen werden herumgeschleudert. Es begann schmerzhaft zu werden. Er zögerte und fragte mich nach Ideen.
Widerwillig fragte ich: "Würden Sie ein Objekt in Betracht ziehen?"
Er schaute zurück zu seinem DTO und verzog verwirrt das Gesicht. "Es ist ein Objekt".
"Ich meine ein echtes Objekt"
"Huh?"
"Lass mich dir etwas zeigen. Du entscheidest, ob es nützlich ist."
Ich wählte einen neuen Namen und peitschte schnell etwas auf, das ein bisschen so aussah:
Dies hat nichts bewirkt, was die statischen Methoden noch nicht getan haben. Aber jetzt hatte ich die 14 statischen Methoden in eine Klasse gesaugt, in der sie mit den Daten, an denen sie arbeiteten, allein sein konnten.
Ich habe meinen Praktikanten nicht gezwungen, es zu benutzen. Ich bot es einfach an und ließ ihn entscheiden, ob er sich an die statischen Methoden halten wollte. Ich ging nach Hause und dachte, er würde sich wahrscheinlich an das halten, was er bereits hatte. Am nächsten Tag stellte ich fest, dass er es an vielen Orten benutzte. Es löschte den Rest des Codes, der immer noch hässlich und prozedural war, aber diese Komplexität war uns jetzt hinter einem Objekt verborgen. Es war ein bisschen besser.
Jetzt ist sicher, dass jedes Mal, wenn Sie darauf zugreifen, einiges an Arbeit geleistet wird. Ein DTO ist ein schöner, schnell zwischengespeicherter Wert. Ich machte mir darüber Sorgen, erkannte jedoch, dass ich das Caching hinzufügen konnte, wenn wir es jemals brauchten, ohne den verwendeten Code zu berühren. Also werde ich mich nicht darum kümmern, bis es uns wichtig ist.
Soll ich damit sagen, dass Sie sich immer an OO-Objekte über DTOs halten sollten? Nein. DTOs leuchten, wenn Sie eine Grenze überschreiten müssen, die Sie davon abhält, Methoden zu verschieben. DTOs haben ihren Platz.
Aber auch OO-Objekte. Erfahren Sie, wie Sie beide Tools verwenden. Erfahren Sie, was jeder kostet. Lernen Sie, das Problem, die Situation und den Praktikanten entscheiden zu lassen. Dogma ist hier nicht dein Freund.
Da meine Antwort bereits lächerlich lang ist, möchte ich Sie bei einer Überprüfung Ihres Codes von einigen Missverständnissen abbringen.
Wo ist dein Konstruktor? Dies zeigt mir nicht genug, um zu wissen, ob es nützlich ist.
Sie können im Namen des Einzelverantwortungsprinzips viele dumme Dinge tun. Ich könnte argumentieren, dass Cat Strings und Cat Ints getrennt werden sollten. Diese Zeichenmethoden und Bilder müssen alle ihre eigene Klasse haben. Dass Ihr laufendes Programm eine einzige Verantwortung ist, sollten Sie nur eine Klasse haben. : P.
Für mich besteht der beste Weg, dem Prinzip der Einzelverantwortung zu folgen, darin, eine gute Abstraktion zu finden, mit der Sie Komplexität in eine Box packen und sie verbergen können. Wenn Sie ihm einen guten Namen geben können, der die Leute davon abhält, überrascht zu werden, was sie finden, wenn sie hineinschauen, haben Sie ihn ziemlich gut verfolgt. Zu erwarten, dass es mehr Entscheidungen diktiert, als es Ärger bereitet. Ehrlich gesagt, beide Code-Listen tun dies, sodass ich nicht verstehe, warum SRP hier wichtig ist.
Nun nein. Beim Open-Close-Prinzip geht es nicht darum, neue Methoden hinzuzufügen. Es geht darum, die Implementierung alter Methoden ändern zu können und nichts bearbeiten zu müssen. Nichts, was dich benutzt und nicht deine alten Methoden. Stattdessen schreiben Sie irgendwo anders neuen Code. Eine Form von Polymorphismus wird das gut machen. Sehen Sie das hier nicht.
Zum Teufel, woher soll ich das wissen? Schauen Sie, es hat Vorteile und Kosten. Wenn Sie Code von Daten trennen, können Sie beide ändern, ohne den anderen neu kompilieren zu müssen. Vielleicht ist Ihnen das von entscheidender Bedeutung. Vielleicht macht es Ihren Code nur sinnlos kompliziert.
Wenn Sie sich dadurch besser fühlen, sind Sie nicht weit von etwas entfernt, das Martin Fowler als Parameterobjekt bezeichnet . Sie müssen nicht nur Grundelemente in Ihr Objekt aufnehmen.
Ich möchte, dass Sie ein Gespür dafür entwickeln, wie Sie Ihre Trennung in beiden Codierungsstilen durchführen oder nicht. Ob Sie es glauben oder nicht, Sie werden nicht gezwungen, einen Stil zu wählen. Sie müssen nur mit Ihrer Wahl leben.
quelle
Sie sind auf ein Debattenthema gestoßen, das seit mehr als einem Jahrzehnt unter Entwicklern zu Argumenten führt. Im Jahr 2003 prägte Martin Fowler den Ausdruck "Anemic Domain Model" (ADM) , um diese Trennung von Daten und Funktionalität zu beschreiben. Er - und andere, die ihm zustimmen - argumentieren, dass "Rich Domain Models" (Mischen von Daten und Funktionalität) "richtige OO" sind und dass der ADM-Ansatz ein Nicht-OO-Anti-Pattern ist.
Es gab immer diejenigen, die dieses Argument zurückwiesen, und diese Seite des Arguments ist in den letzten Jahren immer lauter geworden, da immer mehr Entwickler funktionale Entwicklungstechniken eingeführt haben. Dieser Ansatz fördert aktiv die Trennung von Daten- und Funktionsproblemen. Die Daten sollten so weit wie möglich unveränderlich sein, damit die Einkapselung des veränderlichen Zustands kein Problem darstellt. In solchen Situationen ist es nicht vorteilhaft, Funktionen direkt an diese Daten anzuhängen. Ob es dann "nicht OO" ist oder nicht, ist für solche Leute absolut nicht von Interesse.
Unabhängig davon, auf welcher Seite des Zauns Sie sitzen (ich sitze übrigens sehr fest auf der Seite "Martin Fowler spricht von einer Menge alter Scheiße"), ist Ihre Verwendung statischer Methoden für
printInfo
unddraw
ist nahezu universell verpönt. Statische Methoden sind beim Schreiben von Unit-Tests schwer zu verspotten. Wenn sie also Nebenwirkungen haben (z. B. Drucken oder Zeichnen auf einem Bildschirm oder einem anderen Gerät), sollten sie nicht statisch sein oder den Ausgabeort als Parameter übergeben haben.Sie könnten also eine Schnittstelle haben:
Und eine Implementierung, die zur Laufzeit in den Rest Ihres Systems eingefügt wird (wobei andere Implementierungen zum Testen verwendet werden):
Oder fügen Sie zusätzliche Parameter hinzu, um die Nebenwirkungen dieser Methoden zu beseitigen:
quelle
DTOs - Datentransportobjekte
sind genau dafür nützlich. Wenn Sie Daten zwischen Programmen oder Systemen verschieben möchten, sind DTOs wünschenswert, da sie eine verwaltbare Teilmenge eines Objekts bereitstellen, die sich nur mit Datenstruktur und Formatierung befasst. Der Vorteil besteht darin, dass Sie Aktualisierungen komplexer Methoden nicht über mehrere Systeme hinweg synchronisieren müssen (solange sich die zugrunde liegenden Daten nicht ändern).
Der Sinn von OO besteht darin, Daten und den Code, der auf diese Daten einwirkt, eng miteinander zu verbinden. Das Aufteilen eines logischen Objekts in separate Klassen ist normalerweise eine schlechte Idee.
quelle
Viele Entwurfsmuster und -prinzipien widersprechen sich, und letztendlich hilft Ihnen nur Ihr Urteilsvermögen als guter Programmierer bei der Entscheidung, welche Muster für ein bestimmtes Problem gelten sollen.
Meiner Meinung nach sollte das Prinzip " Das Einfachste tun , das möglicherweise funktionieren könnte" standardmäßig gewinnen, es sei denn, Sie haben einen starken Grund, das Design komplizierter zu gestalten. In Ihrem speziellen Beispiel würde ich mich also für das erste Design entscheiden, bei dem es sich um einen traditionelleren objektorientierten Ansatz handelt, bei dem Daten und Funktionen in derselben Klasse zusammengefasst sind.
Hier sind einige Fragen, die Sie sich zur Anwendung stellen können:
Wenn Sie eine dieser Fragen mit Ja beantworten, erhalten Sie möglicherweise ein komplexeres Design mit separaten DTO- und Funktionsklassen.
quelle
Ist es ein gutes Muster?
Sie werden hier wahrscheinlich unterschiedliche Antworten erhalten, weil die Menschen unterschiedlichen Denkrichtungen folgen. Also noch bevor ich anfange: Diese Antwort entspricht der objektorientierten Programmierung, wie sie von Robert C. Martin im Buch "Clean Code" beschrieben wird.
"True" OOP
Soweit mir bekannt ist, gibt es zwei Interpretationen von OOP. Für die meisten Menschen läuft es darauf hinaus, dass "ein Objekt eine Klasse ist".
Die andere Denkrichtung fand ich jedoch hilfreicher: "Ein Objekt ist etwas, das etwas tut". Wenn Sie mit Klassen arbeiten, ist jede Klasse eines von zwei Dingen: ein " Datenhalter " oder ein Objekt . Dateninhaber halten Daten (duh), Objekte machen Sachen. Das bedeutet nicht , dass ein Dateninhaber keine Methoden haben kann! Denken Sie an ein
list
Alist
nicht wirklich tun etwas, aber es hat Methoden zur Durchsetzung der Art und Weise sie die Daten hält .Dateninhaber
Sie
Cat
sind ein Paradebeispiel für einen Dateninhaber. Sicher, außerhalb Ihres Programms ist eine Katze etwas, das etwas tut , aber innerhalb Ihres Programms ist aCat
ein Stringname
, ein Intweight
und ein Imageimage
.Dass Dateninhaber nichts tun , bedeutet auch nicht, dass sie keine Geschäftslogik enthalten! Wenn Ihre
Cat
Klasse einen Konstruktor hat, der dies erfordertname
,weight
undimage
Sie die Geschäftsregel, dass jede Katze diese hat, erfolgreich gekapselt haben. Wenn Sie das ändern können,weight
nachdem eineCat
Klasse erstellt wurde, ist dies eine weitere gekapselte Geschäftsregel. Dies sind sehr grundlegende Dinge, aber das bedeutet nur, dass es sehr wichtig ist, sie nicht falsch zu verstehen - und auf diese Weise stellen Sie sicher, dass es unmöglich ist , dies falsch zu verstehen.Objekte
Objekte machen etwas. Wenn Sie Clean * -Objekte möchten , können wir dies in "Objekte tun eine Sache" ändern .
Das Drucken der Katzeninformationen ist die Aufgabe eines Objekts. Sie können dieses Objekt beispielsweise
CatInfoLogger
mit einer öffentlichen Methode " " aufrufenLog(Cat)
. Das ist alles, was wir von außen wissen müssen: Dieses Objekt protokolliert die Informationen einer Katze und benötigt dazu eineCat
.Im Inneren würde dieses Objekt Verweise auf alles enthalten, was es benötigt, um seine alleinige Verantwortung für die Protokollierung von Katzen zu erfüllen . In diesem Fall ist dies nur ein Verweis auf
System
forSystem.out.println
, in den meisten Fällen handelt es sich jedoch um private Verweise auf andere Objekte. Wenn die Logik zum Formatieren der Ausgabe vor dem Drucken zu komplex wird,CatInfoLogger
kann nur ein Verweis auf ein neues Objekt abgerufen werdenCatInfoFormatter
. Wenn später jedes Protokoll auch in eine Datei geschrieben werden muss, können Sie einCatToFileLogger
Objekt hinzufügen, das dies tut.Dateninhaber gegen Objekte - Zusammenfassung
Warum sollten Sie das alles tun? Denn jetzt ändert das Ändern einer Klasse nur eines ( die Art und Weise, wie eine Katze im Beispiel protokolliert wird ). Wenn Sie ein Objekt ändern, ändern Sie nur die Art und Weise, wie eine bestimmte Aktion ausgeführt wird. Wenn Sie einen Dateninhaber wechseln, ändern Sie nur, welche Daten gespeichert werden und / oder auf welche Weise sie gespeichert werden.
Das Ändern von Daten kann bedeuten, dass sich auch die Art und Weise ändern muss, in der etwas getan wird. Diese Änderung wird jedoch erst dann vorgenommen, wenn Sie sie selbst ändern, indem Sie das verantwortliche Objekt ändern.
Overkill?
Diese Lösung scheint ein wenig übertrieben zu sein. Lassen Sie mich ehrlich sein: Es ist . Wenn Ihr gesamtes Programm nur so groß wie das Beispiel ist, führen Sie keine der oben genannten Aktionen aus - wählen Sie einfach eine der vorgeschlagenen Methoden mit einem Münzwurf aus. Es besteht keine Gefahr der Verwechslung anderen Code , wenn es ist kein anderer Code.
Jede "ernsthafte" Software (= mehr als ein Skript oder 2) ist jedoch wahrscheinlich groß genug, um von einer solchen Struktur zu profitieren.
Antworten
"Die meisten Klassen" (ohne weitere Qualifikation) in DTOs / Dateninhaber umzuwandeln, ist kein Anti-Muster, da es eigentlich kein Muster ist - es ist mehr oder weniger zufällig.
Das Auseinanderhalten von Daten und Objekten wird jedoch als eine gute Möglichkeit angesehen, Ihren Code sauber zu halten *. Manchmal brauchen Sie nur einen flachen, "anämischen" DTO und das wars. In anderen Fällen benötigen Sie Methoden, um die Speicherung der Daten zu erzwingen. Setzen Sie die Funktionalität Ihres Programms nur nicht mit den Daten in Verbindung.
* Das ist "sauber" mit einem Großbuchstaben C wie dem Buchtitel, denn - denken Sie an den ersten Absatz - es handelt sich um eine Denkschule, während es auch andere gibt.
quelle