it-swarm.com.de

Verstößt dieses Klassendesign gegen das Prinzip der Einzelverantwortung?

Heute hatte ich einen Streit mit jemandem.

Ich erklärte die Vorteile eines Rich-Domain-Modells im Gegensatz zu einem anämischen Domain-Modell. Und ich habe meinen Standpunkt mit einer einfachen Klasse demonstriert, die so aussieht:

public class Employee
{
    public Employee(string firstName, string lastName)
    {
        FirstName = firstName;
        LastName = lastname;
    }

    public string FirstName { get private set; }
    public string LastName { get; private set;}
    public int CountPaidDaysOffGranted { get; private set;}

    public void AddPaidDaysOffGranted(int numberOfdays)
    {
        // Do stuff
    }
}

Als er seinen anämischen Modellansatz verteidigte, war eines seiner Argumente: "Ich glaube an SOLID . Sie verletzen das Prinzip der Einzelverantwortung) (SRP), da Sie beide Daten darstellen und Logik in derselben Klasse ausführen. "

Ich fand diese Behauptung wirklich überraschend, da nach dieser Überlegung jede Klasse mit einer Eigenschaft und einer Methode gegen die SRP verstößt und daher OOP im Allgemeinen nicht SOLID ist und funktionale Programmierung ist der einzige Weg zum Himmel.

Ich habe beschlossen, nicht auf seine vielen Argumente zu antworten, aber ich bin gespannt, was die Community zu dieser Frage denkt.

Wenn ich geantwortet hätte, hätte ich zunächst auf das oben erwähnte Paradoxon hingewiesen und dann angegeben, dass die SRP stark von der Granularität abhängt, die Sie berücksichtigen möchten, und dass, wenn Sie weit genug gehen, jede Klasse mehr als eine enthält Eigenschaft oder eine Methode verletzt es.

Was hättest du gesagt?

Update: Das Beispiel wurde von Guntbert großzügig aktualisiert, um die Methode realistischer zu gestalten und uns auf die zugrunde liegende Diskussion zu konzentrieren.

63
tobiak777

Einzelverantwortung sollte als Abstraktion logischer Aufgaben in Ihrem System verstanden werden. Eine Klasse sollte die einzige Verantwortung haben (alles Notwendige tun, um) eine einzige spezifische Aufgabe auszuführen. Dies kann tatsächlich viel in eine gut gestaltete Klasse bringen, abhängig von der Verantwortung. Die Klasse, in der beispielsweise Ihre Skript-Engine ausgeführt wird, kann viele Methoden und Daten enthalten, die an der Verarbeitung von Skripten beteiligt sind.

Ihr Mitarbeiter konzentriert sich auf das Falsche. Die Frage ist nicht "Welche Mitglieder hat diese Klasse?" aber "welche nützlichen Operationen führt diese Klasse innerhalb des Programms aus?" Sobald dies verstanden ist, sieht Ihr Domain-Modell gut aus.

68
Mason Wheeler

Das Prinzip der Einzelverantwortung befasst sich nur damit, ob ein Teil des Codes (in OOP sprechen wir normalerweise von Klassen) die Verantwortung für einen Teil der Funktionalität hat. ). Ich denke, Ihr Freund, der sagt, dass Funktionen und Daten sich nicht vermischen können, hat diese Idee nicht wirklich verstanden. Wenn Employee auch Informationen über seinen Arbeitsplatz enthalten würde, wie schnell sein Auto fährt und welche Art von Futter sein Hund frisst, hätten wir ein Problem.

Da sich diese Klasse nur mit einem Employee befasst, ist es fair zu sagen, dass sie nicht offen gegen SRP verstößt, aber die Leute werden immer ihre eigenen Meinungen haben.

Ein Ort, an dem wir uns verbessern könnten, besteht darin, Mitarbeiterinformationen (wie Name, Telefonnummer, E-Mail-Adresse) von seinem Urlaub zu trennen. In meinen Augen bedeutet dies bedeutet nicht, dass sich Methoden und Daten nicht miteinander vermischen können, es bedeutet nur, dass sich die Urlaubsfunktionalität möglicherweise an einem separaten Ort befindet.

41
Frank Bryce

Es gibt bereits gute Antworten, die darauf hinweisen, dass SRP ein abstraktes Konzept zur logischen Funktionalität ist, aber es gibt subtilere Punkte, die meiner Meinung nach hinzugefügt werden sollten.

In den ersten beiden Buchstaben in SOLID, SRP und OCP, geht es darum, wie sich Ihr Code als Reaktion auf eine Änderung der Anforderungen ändert. Meine Lieblingsdefinition des SRP lautet: "Ein Modul/eine Klasse/eine Funktion sollte nur einen Grund zur Änderung haben." Das Streiten über wahrscheinliche Gründe für die Änderung Ihres Codes ist viel produktiver als das Streiten darüber, ob Ihr Code SOLID] ist oder nicht.

Wie viele Gründe muss sich Ihre Mitarbeiterklasse ändern? Ich weiß es nicht, weil ich den Kontext nicht kenne, in dem Sie es verwenden, und ich kann auch die Zukunft nicht sehen. Was ich tun kann, ist ein Brainstorming möglicher Änderungen basierend auf dem, was ich in der Vergangenheit gesehen habe, und Sie können subjektiv bewerten, wie wahrscheinlich sie sind. Wenn mehr als eine Punktzahl zwischen "ziemlich wahrscheinlich" und "Mein Code hat sich aus genau diesem Grund bereits geändert" liegt, verstoßen Sie gegen SRP gegen diese Art von Änderung. Hier ist eine: Jemand mit mehr als zwei Namen tritt Ihrem Unternehmen bei (oder ein Architekt liest dieser ausgezeichnete W3C-Artikel ). Folgendes ist noch zu beachten: Ihr Unternehmen ändert die Zuordnung von Feiertagen.

Beachten Sie, dass diese Gründe auch dann gelten, wenn Sie die AddHolidays-Methode entfernen. Viele anämische Domänenmodelle verletzen die SRP. Viele von ihnen sind nur Datenbanktabellen im Code, und Datenbanktabellen haben häufig mehr als 20 Gründe, sich zu ändern.

Hier ist etwas zum Kauen: Würde sich Ihre Mitarbeiterklasse ändern, wenn Ihr System die Gehälter der Mitarbeiter verfolgen müsste? Adressen? Notfall-Kontaktinformationen? Wenn Sie zu zwei von ihnen "Ja" (und "wahrscheinlich") sagen würden, würde Ihre Klasse gegen SRP verstoßen, selbst wenn noch kein Code darin wäre! SOLID handelt von Prozessen und Architektur ebenso wie von Code.

20
Carl Leth

Meiner Meinung nach könnte diese Klasse möglicherweise gegen SRP verstoßen, wenn sie weiterhin sowohl ein Employee als auch ein EmployeeHolidays darstellt.

So wie es ist und wenn es für Peer Review zu mir käme, würde ich es wahrscheinlich durchlassen. Wenn mehr mitarbeiterspezifische Eigenschaften und Methoden hinzugefügt würden und mehr urlaubsspezifische Eigenschaften hinzugefügt würden, würde ich wahrscheinlich eine Aufteilung unter Berufung auf SRP und ISP empfehlen.

19
NikolaiDante

Dass die Klasse Daten darstellt, liegt nicht in der Verantwortung der Klasse, sondern ist ein privates Implementierungsdetail.

Die Klasse hat eine Verantwortung, einen Mitarbeiter zu vertreten. In diesem Zusammenhang bedeutet dies, dass eine öffentliche API bereitgestellt wird, die Ihnen Funktionen bietet, die Sie für den Umgang mit Mitarbeitern benötigen (ob AddHolidays ein gutes Beispiel ist, ist umstritten).

Die Implementierung ist intern; es kommt also vor, dass dies einige private Variablen und etwas Logik benötigt. Das bedeutet nicht, dass die Klasse jetzt mehrere Verantwortlichkeiten hat.

9
RemcoGerlich

Die Idee, dass das Mischen von Logik und Daten in irgendeiner Weise immer falsch ist, ist so lächerlich, dass sie nicht einmal eine Diskussion verdient. Es gibt zwar eine eindeutige Verletzung der Einzelverantwortung im Beispiel, aber nicht, weil es eine Eigenschaft DaysOfHolidays und eine Funktion AddHolidays(int) gibt.

Dies liegt daran, dass sich die Identität des Mitarbeiters mit dem Urlaubsmanagement vermischt, was schlecht ist. Die Identität des Mitarbeiters ist eine komplexe Sache, die erforderlich ist, um Urlaub, Gehalt, Überstunden zu erfassen, um darzustellen, wer in welchem ​​Team ist, um auf Leistungsberichte zu verlinken usw. Ein Mitarbeiter kann auch den Vor- und Nachnamen oder beides ändern und gleich bleiben Mitarbeiter. Mitarbeiter können sogar mehrere Schreibweisen ihres Namens haben, z. B. eine ASCII und eine Unicode-Schreibweise). Personen können 0 bis n Vor- und/oder Nachnamen haben. Sie können in verschiedenen Gerichtsbarkeiten unterschiedliche Namen haben. - Die Verfolgung der Identität eines Mitarbeiters reicht aus, um Urlaub oder Urlaubsmanagement nicht zusätzlich hinzuzufügen, ohne es als zweite Verantwortung zu bezeichnen.

5
Peter

"Ich glaube an SOLID. Sie verstoßen gegen das Single-Responsibility-Prinzip (SRP), da Sie sowohl Daten darstellen als auch Logik in derselben Klasse ausführen."

Wie die anderen bin ich damit nicht einverstanden.

Ich würde sagen, dass die SRP verletzt wird, wenn Sie mehr als eine Logik in der Klasse ausführen. Wie viele Daten in der Klasse gespeichert werden müssen, um diese einzelne Logik zu erreichen, ist irrelevant.

Ich finde es heutzutage nicht nützlich, darüber zu debattieren, was eine einzige Verantwortung oder einen einzigen Grund zur Änderung darstellt und was nicht. Ich würde an seiner Stelle ein Minimum-Trauer-Prinzip vorschlagen:

Prinzip der minimalen Trauer: Code sollte entweder versuchen, die Wahrscheinlichkeit zu minimieren, dass Änderungen erforderlich sind, oder die Leichtigkeit der Änderung maximieren.

Wie ist das? Es sollte kein Raketenwissenschaftler erforderlich sein, um herauszufinden, warum dies zur Reduzierung der Wartungskosten beitragen kann, und hoffentlich sollte dies kein Punkt endloser Debatten sein, aber wie bei SOLID im Allgemeinen ist dies nicht der Fall Überall blind anwenden. Es ist etwas zu beachten, während Kompromisse abgewogen werden.

Die Wahrscheinlichkeit, dass Änderungen erforderlich sind, hängt von folgenden Faktoren ab:

  1. Gute Tests (verbesserte Zuverlässigkeit).
  2. Es wird nur der Mindestcode einbezogen, der erforderlich ist, um etwas Bestimmtes zu tun (dies kann das Reduzieren afferenter Kopplungen umfassen).
  3. Machen Sie den Code einfach zu dem, was er tut (siehe Make Badass-Prinzip).

Die Schwierigkeit, Änderungen vorzunehmen, geht mit efferenten Kopplungen einher. Das Testen führt zu efferenten Kopplungen, verbessert jedoch die Zuverlässigkeit. Gut gemacht, tut es im Allgemeinen mehr gut als schaden und ist völlig akzeptabel und wird durch das Prinzip der minimalen Trauer gefördert.

Make Badass-Prinzip: Klassen, die an vielen Orten verwendet werden, sollten großartig sein. Sie sollten zuverlässig und effizient sein, wenn dies mit ihrer Qualität usw. zusammenhängt.

Und das Make Badass-Prinzip ist an das Minimum Grief-Prinzip gebunden, da Badass-Dinge eine geringere Wahrscheinlichkeit haben, Änderungen zu erfordern, als Dinge, die an dem, was sie tun, scheiße sind.

Ich hätte zunächst auf das oben erwähnte Paradoxon hingewiesen und dann darauf hingewiesen, dass die SRP stark von der Granularität abhängt, die Sie berücksichtigen möchten, und dass jede Klasse, die mehr als eine Eigenschaft oder eine Methode enthält, verletzt, wenn Sie weit genug gehen es.

Aus SRP-Sicht hätte eine Klasse, die kaum etwas tut, sicherlich nur einen (manchmal null) Grund, sich zu ändern:

class Float
{
public:
    explicit Float(float val);
    float get() const;
    void set(float new_val);
};

Das hat praktisch keinen Grund, sich zu ändern! Es ist besser als SRP. Es ist ZRP!

Außer ich würde vorschlagen, dass es eine offensichtliche Verletzung des Make Badass-Prinzips ist. Es ist auch absolut wertlos. Etwas, das so wenig tut, kann nicht hoffen, schlecht zu sein. Es enthält zu wenig Informationen (TLI). Und wenn Sie etwas haben, das TLI ist, kann es natürlich nichts wirklich Sinnvolles tun, auch nicht mit den darin enthaltenen Informationen. Daher muss es an die Außenwelt weitergegeben werden, in der Hoffnung, dass jemand anderes tatsächlich etwas Sinnvolles und Schlechtes tut. Und diese Undichtigkeit ist in Ordnung für etwas, das nur dazu gedacht ist, Daten und nichts weiter zu aggregieren, aber diese Schwelle ist der Unterschied, wie ich zwischen "Daten" und "Objekten" sehe.

Natürlich ist auch etwas, das TMI ist, schlecht. Wir könnten unsere gesamte Software in eine Klasse einordnen. Es kann sogar nur eine run Methode haben. Und jemand könnte sogar argumentieren, dass es jetzt einen sehr allgemeinen Grund für eine Änderung gibt: "Diese Klasse muss nur geändert werden, wenn die Software verbessert werden muss." Ich bin dumm, aber natürlich können wir uns alle Wartungsprobleme damit vorstellen.

Es gibt also einen Balanceakt hinsichtlich der Granularität oder Grobheit der von Ihnen entworfenen Objekte. Ich beurteile es oft daran, wie viele Informationen Sie an die Außenwelt weitergeben müssen und wie viele sinnvolle Funktionen sie ausführen können. Ich finde das Make Badass-Prinzip dort oft hilfreich, um das Gleichgewicht zu finden, während ich es mit dem Minimum Grief-Prinzip kombiniere.

2
user204677

Im Gegenteil, für mich bricht das anämische Domänenmodell einige OOP] Hauptkonzepte (die Attribute und Verhalten miteinander verbinden), kann jedoch aufgrund architektonischer Entscheidungen unvermeidlich sein. Anämische Domänen sind leichter zu denken, weniger organisch und sequentieller.

Viele Systeme neigen dazu, dies zu tun, wenn mehrere Schichten mit denselben Daten spielen müssen (Service-Schicht, Web-Schicht, Client-Schicht, Agenten ...).

Es ist einfacher, die Datenstruktur an einer Stelle und das Verhalten in anderen Serviceklassen zu definieren. Wenn dieselbe Klasse auf mehreren Ebenen verwendet wurde, kann diese groß werden, und es wird die Frage gestellt, welche Ebene für die Definition des erforderlichen Verhaltens verantwortlich ist und wer die Methoden aufrufen kann.

Zum Beispiel ist dies möglicherweise keine gute Idee als ein Agent-Prozess, der Statistiken über alle Ihre Mitarbeiter berechnet, die eine Berechnung für bezahlte Tage aufrufen können. Und die GUI der Mitarbeiterliste benötigt mit Sicherheit überhaupt nicht die neue aggregierte ID-Berechnung, die in diesem Statistikagenten verwendet wird (und die damit verbundenen technischen Daten). Wenn Sie die Methoden auf diese Weise trennen, enden Sie im Allgemeinen mit einer Klasse mit nur Datenstrukturen.

Sie können die "Objekt" -Daten oder nur einige von ihnen oder in ein anderes Format (json) zu einfach serialisieren/deserialisieren ... ohne sich um ein Objektkonzept/eine Objektverantwortung kümmern zu müssen. Es sind jedoch nur Datenübertragungen. Sie können jederzeit Daten zwischen zwei Klassen zuordnen (Employee, EmployeeVO, EmployeeStatistic…), aber was bedeutet Employee hier wirklich?

Also ja, es trennt Daten vollständig in Domänenklassen und Datenverarbeitung in Serviceklassen, aber es wird hier benötigt. Ein solches System ist gleichzeitig ein funktionales System, um geschäftlichen Nutzen zu bringen, und ein technisches System, um die Daten überall dort zu verbreiten, wo sie benötigt werden, während ein angemessener Verantwortungsbereich beibehalten wird (und ein verteiltes Objekt löst dies auch nicht).

Wenn Sie keine Verhaltensbereiche trennen müssen, können Sie die Methoden je nach Ansicht Ihres Objekts in Serviceklassen oder Domänenklassen einfügen. Ich neige dazu, ein Objekt als das "echte" Konzept zu betrachten. Dies hilft natürlich dabei, die SRP beizubehalten. In Ihrem Beispiel ist es also realistischer, als wenn der Chef des Mitarbeiters seinem PayDayAccount einen Zahltag-Rabatt hinzufügt. Ein Mitarbeiter wird von der Firma Works eingestellt, kann krank sein oder um Rat gefragt werden, und er hat ein Zahltagkonto (der Chef kann es direkt von ihm oder von einer PayDayAccount-Registrierung abrufen ...). Sie können jedoch eine aggregierte Verknüpfung erstellen hier der Einfachheit halber, wenn Sie für eine einfache Software nicht zu viel Komplexität wünschen.

1
Vince

Sie verstoßen gegen das Single Responsibility-Prinzip (SRP), da Sie sowohl Daten darstellen als auch Logik in derselben Klasse ausführen.

Das klingt für mich sehr vernünftig. Das Modell verfügt möglicherweise nicht über öffentliche Eigenschaften, wenn Aktionen verfügbar gemacht werden. Es handelt sich im Grunde genommen um eine Idee zur Trennung von Befehlen und Abfragen. Bitte beachten Sie, dass Command mit Sicherheit einen privaten Status hat.

0
Dmitry Nogin

Sie können das Prinzip der Einzelverantwortung nicht verletzen, da es nur ein ästhetisches Kriterium ist, keine Naturregel. Lassen Sie sich nicht durch den wissenschaftlich klingenden Namen und die Großbuchstaben irreführen.

0
ZunTzu