Ist die Trennung der meisten Klassen in Klassen nur für Datenfelder und Klassen nur für Methoden (wenn möglich) ein gutes oder ein Anti-Muster?

10

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?

ocomfd
quelle
15
Überall blind etwas zu tun, weil man ein neues Konzept gelernt hat, ist immer ein Anti-Muster.
Kayaman
4
Was wäre der Sinn von Klassen, wenn es immer besser wäre, Daten von Methoden zu trennen, die sie ändern? Das klingt nach nicht objektorientierter Programmierung (die sicherlich ihren Platz hat). Da dies als "objektorientiert" gekennzeichnet ist, gehe ich davon aus, dass Sie die von OOP bereitgestellten Tools verwenden möchten.
user1118321
4
Eine weitere Frage, die beweist, dass SRP so stark missverstanden wird, dass es verboten werden sollte. Es liegt nicht in der Verantwortung, Daten in einer Reihe von öffentlichen Feldern zu speichern .
user949300
1
@ user949300, wenn die Klasse für diese Felder verantwortlich ist, hat das Verschieben an eine andere Stelle in der App natürlich keine Auswirkungen. Oder anders ausgedrückt, Sie irren sich: Sie sind zu 100% eine Verantwortung.
David Arno
2
@DavidArno und R Schmitz: Das Enthalten von Feldern gilt nicht als Verantwortung für die Zwecke von SRP. Wenn dies der Fall wäre, könnte es keine OOP geben, da alles ein DTO wäre, und dann würden separate Klassen Prozeduren enthalten, um an den Daten zu arbeiten. Eine einzige Verantwortung betrifft einen einzelnen Stakeholder . (Obwohl sich dies bei jeder Iteration des Principals geringfügig zu ändern scheint)
user949300

Antworten:

10

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:

public class Cat{
    private String name;
    private int weight;
    private Image image;

    public String getInfo(){
        return "Name:"+this.name+",weight:"+this.weight;
    }
    public Image getImage(){
        return image;
    }
}

Jetzt Catbietet das Objekt genügend Logik, um den umgebenden Code einfach zu implementieren printInfound drawohne alle Attribute öffentlich zugänglich zu machen. Der richtige Ort für diese beiden Methoden ist höchstwahrscheinlich keine Gottklasse CatMethods(da printInfound drawhöchstwahrscheinlich unterschiedliche Anliegen sind, daher halte ich es für sehr unwahrscheinlich, dass sie zur selben Klasse gehören).

Ich kann mir eine CatDrawingControllerImplementierung vorstellen draw(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 verwendet getInfo(daher printInfokann dies in diesem Zusammenhang veraltet sein). Aber um vernünftige Entscheidungen zu treffen, muss man den Kontext kennen und wissen, wie die CatKlasse 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 CatKlasse keine Setter verfügbar macht und Imageselbst unveränderlich ist, kann mit diesem Entwurf Catunverä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.

Doc Brown
quelle
Löse fröhliche Downvoter aus, das wird langweilig. Wenn Sie Kritiker haben, lassen Sie es mich bitte wissen. Gerne kläre ich Missverständnisse, wenn Sie welche haben.
Doc Brown
Obwohl ich Ihrer Antwort im Allgemeinen zustimme ... getInfo()könnte es für jemanden, der diese Frage stellt, irreführend sein. Es mischt subtil Präsentationsverantwortlichkeiten wie es printInfo()tut und besiegt den Zweck der DrawingControllerKlasse
Adriano Repetti
@AdrianoRepetti Ich sehe nicht, dass es den Zweck von zunichte macht DrawingController. Ich stimme dem zu getInfo()und bin printInfo()schreckliche Namen. Betrachten toString()undprintTo(Stream stream)
candied_orange
@candid Es geht nicht (nur) um den Namen, sondern darum, was er tut. Bei diesem Code handelt es sich nur um ein Präsentationsdetail, und wir haben die Ausgabe effektiv nur abstrahiert, nicht den verarbeiteten Inhalt und seine Logik.
Adriano Repetti
@AdrianoRepetti: Ehrlich gesagt ist dies nur ein erfundenes Beispiel, um zur ursprünglichen Frage zu passen und meinen Standpunkt zu demonstrieren. In einem realen System wird es einen umgebenden Kontext geben, der es natürlich ermöglicht, Entscheidungen über bessere Methoden und Namen zu treffen.
Doc Brown
6

Späte Antwort, aber ich kann nicht widerstehen.

Ist X die meisten Klassen in Y gut oder ein Anti-Muster?

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:

public class Cat{
    CatData(string catPage) {
        this.catPage = catPage
    }
    private readonly string catPage;

    public string name() { return chop("name prefix", "name suffix"); }
    public string weight() { return chop("weight prefix", "weight suffix"); }
    public string image() { return chop("image prefix", "image suffix"); }

    private string chop(string prefix, string suffix) {
        int start = catPage.indexOf(prefix) + prefix.Length;
        int end = catPage.indexOf(suffix);
        int length = end - start;
        return catPage.Substring(start, length);
    }
}

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.

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
    }
}

Wo ist dein Konstruktor? Dies zeigt mir nicht genug, um zu wissen, ob es nützlich ist.

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).

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.

Und es passt auch zum Open-Closed-Prinzip, da durch Hinzufügen neuer Methoden die CatData-Klasse nicht geändert werden muss.

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.

Meine Frage ist, ist es ein gutes oder ein Anti-Muster?

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.

candied_orange
quelle
2

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 printInfound drawist 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:

public interface CatMethods {
    void printInfo(Cat cat);
    void draw(Cat cat);
}

Und eine Implementierung, die zur Laufzeit in den Rest Ihres Systems eingefügt wird (wobei andere Implementierungen zum Testen verwendet werden):

internal class CatMethodsForScreen implements CatMethods {
    public void printInfo(Cat cat) {
        System.out.println("Name:"+cat.name+",weight:"+cat.weight);
    }

    public void draw(Cat cat) {
        //some draw code which uses cat.image
    }
}

Oder fügen Sie zusätzliche Parameter hinzu, um die Nebenwirkungen dieser Methoden zu beseitigen:

public static class CatMethods {
    public static void printInfo(Cat cat, OutputHandler output) {
        output.println("Name:"+cat.name+",weight:"+cat.weight);
    }

    public static void draw(Cat cat, Canvas canvas) {
        //some draw code which uses cat.image and draws it on canvas
    }
}
David Arno
quelle
Aber warum sollten Sie versuchen, eine OO-Sprache wie Java in eine funktionale Sprache umzuwandeln, anstatt von Anfang an eine funktionale Sprache zu verwenden? Ist es eine Art "Ich hasse Java, aber jeder benutzt es, also muss ich es, aber zumindest werde ich es auf meine eigene Weise schreiben". Es sei denn, Sie behaupten, das OO-Paradigma sei veraltet und in irgendeiner Weise veraltet. Ich abonniere die Denkschule "Wenn du X willst, weißt du, wo du es bekommen kannst".
Kayaman
@ Kayaman, " Ich abonniere die" Wenn Sie X wollen, wissen Sie, wo Sie es bekommen "Schule des Denkens ". Ich nicht. Ich finde diese Einstellung sowohl kurzsichtig als auch leicht beleidigend. Ich benutze kein Java, aber ich benutze C # und ignoriere einige der "echten OO" -Funktionen. Ich interessiere mich nicht für Vererbung, zustandsbehaftete Objekte und dergleichen und schreibe viele statische Methoden und versiegelte (endgültige) Klassen. Die Tooling- und Community-Größe um C # ist unübertroffen. Für F # ist es viel ärmer. Deshalb abonniere ich die Denkschule "Wenn Sie X möchten, bitten Sie darum, dass es zu Ihrem aktuellen Werkzeug hinzugefügt wird".
David Arno
1
Ich würde sagen, das Ignorieren von Aspekten einer Sprache unterscheidet sich stark von dem Versuch, Funktionen aus anderen Sprachen zu kombinieren. Ich versuche auch nicht, "echtes OO" zu schreiben, da der Versuch, Regeln (oder Prinzipien) in einer ungenauen Wissenschaft wie dem Programmieren zu befolgen, nicht funktioniert. Natürlich müssen Sie die Regeln kennen, damit Sie sie brechen können. Das blinde Anheften von Funktionen kann sich negativ auf die Entwicklung der Sprache auswirken. Nichts für ungut, aber meiner Meinung nach scheint ein Teil der FP-Szene zu glauben, dass "FP das Beste seit geschnittenem Brot ist, warum verstehen die Leute es nicht". Ich würde niemals sagen (oder denken), dass OO oder Java "das Beste" ist.
Kayaman
1
@ Kayaman, aber was soll "versuchen, Features aus anderen Sprachen zu mischen und anzupassen" bedeuten? Argumentieren Sie, dass Funktionsfunktionen nicht zu Java hinzugefügt werden sollten, da "Java eine OO-Sprache ist"? Wenn ja, ist das meiner Ansicht nach ein kurzsichtiges Argument. Lassen Sie die Sprache mit den Wünschen der Entwickler weiterentwickeln. Sie zu zwingen, stattdessen in eine andere Sprache zu wechseln, ist meiner Meinung nach der falsche Ansatz. Sicher, einige "FP-Befürworter" übertreiben, dass FP das Beste ist, was es je gab. Und einige "OO-Befürworter" übertreiben, dass OO "den Kampf gewonnen hat, weil er eindeutig überlegen ist". Ignoriere sie beide.
David Arno
1
Ich bin kein Anti-FP. Ich benutze es in Java, wo es nützlich ist. Aber wenn ein Programmierer "Ich will das" sagt, ist es wie ein Kunde, der "Ich will das" sagt. Programmierer sind keine Sprachdesigner und Kunden sind keine Programmierer. Ich habe Ihre Antwort positiv bewertet, da Sie sagen, dass die "Lösung" von OP verpönt ist. Der Kommentar in der Frage ist jedoch prägnanter, dh er hat die prozedurale Programmierung in einer OO-Sprache neu erfunden. Die allgemeine Idee hat jedoch einen Sinn, wie Sie gezeigt haben. Ein nicht anämisches Domänenmodell bedeutet nicht, dass Daten und Verhalten exklusiv sind, und externe Renderer oder Präsentatoren wären verboten.
Kayaman
0

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.

James Anderson
quelle
-1

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:

  • Muss ich jemals eine alternative Methode zum Rendern eines Katzenbilds bereitstellen? Wenn ja, ist Vererbung kein bequemer Weg, dies zu tun?
  • Muss meine Cat-Klasse von einer anderen Klasse erben oder eine Schnittstelle erfüllen?

Wenn Sie eine dieser Fragen mit Ja beantworten, erhalten Sie möglicherweise ein komplexeres Design mit separaten DTO- und Funktionsklassen.

Jordan Rieger
quelle
-1

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 listA listnicht wirklich tun etwas, aber es hat Methoden zur Durchsetzung der Art und Weise sie die Daten hält .

Dateninhaber

Sie Catsind ein Paradebeispiel für einen Dateninhaber. Sicher, außerhalb Ihres Programms ist eine Katze etwas, das etwas tut , aber innerhalb Ihres Programms ist a Catein String name, ein Int weightund ein Image image.

Dass Dateninhaber nichts tun , bedeutet auch nicht, dass sie keine Geschäftslogik enthalten! Wenn Ihre CatKlasse einen Konstruktor hat, der dies erfordert name, weightund imageSie die Geschäftsregel, dass jede Katze diese hat, erfolgreich gekapselt haben. Wenn Sie das ändern können, weightnachdem eine CatKlasse 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 CatInfoLoggermit einer öffentlichen Methode " " aufrufen Log(Cat). Das ist alles, was wir von außen wissen müssen: Dieses Objekt protokolliert die Informationen einer Katze und benötigt dazu eine Cat.

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 Systemfor System.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, CatInfoLoggerkann nur ein Verweis auf ein neues Objekt abgerufen werden CatInfoFormatter. Wenn später jedes Protokoll auch in eine Datei geschrieben werden muss, können Sie ein CatToFileLoggerObjekt 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

Ist die Trennung der meisten Klassen in DTO- und Hilfsklassen [...] ein gutes oder ein Anti-Muster?

"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.

R. Schmitz
quelle