In einer kürzlich erschienenen Ausgabe des Podcasts DotNet & More haben wir mit Maxim Arshinov seinen bevorstehenden Bericht über Moskau besprochen. Weiter "Der Glanz und die Armut des Subjektmodells". Die Position von Maxim ist direkt auf der Konferenz leicht zugänglich. Darüber hinaus möchte ich die Vision der großen Debatte Anemic VS "Rich" ("Rich") -Domänenmodelle durch das Prisma der einst beliebten GRASP-Vorlagen betrachten
Diskussionen gibt es schon lange, zum Beispiel:
Bevor ich mit der Analyse beginne, möchte ich das Thema des Streits klären. Der Hauptunterschied zwischen dem anämischen und dem reichen Modell besteht darin, dass es keine Geschäftslogik im Hauptteil der Klasse enthält. Ein bekanntes Beispiel für das anämische Modell kann das bekannte DTO-Muster sein.
Ein „reichhaltiges“ Modell kann wiederum eine Logik enthalten, die Geschäftsregeln, Funktionen usw. beschreibt. Bitte beachten Sie, dass wir die Methoden berücksichtigen, die die Geschäftslogik widerspiegeln. ToString, GetHashCode und andere "technische" Teile von Klassen werden nicht in die Diskussion einbezogen und daher ignoriert.
GRASP
Wie am Anfang des Artikels erwähnt, werden wir dieses Problem im Zusammenhang mit GRASP-Mustern betrachten . Dieser Satz von Vorlagen wurde in dem Buch von Craig Larman „Die Verwendung von UML- und Entwurfsmustern“ vorgestellt und hat die moderne Programmierung stark beeinflusst. Beispielsweise wurden die Regeln für niedrige Kopplung / hohe Kohäsion in GRASP angekündigt.
Für Personen, die mit GoF-Mustern vertraut sind, scheinen diese Muster zu verschwommen zu sein. Die Sache ist, dass sich GRASP-Muster nicht auf die Lösung angewandter Probleme konzentrieren, sondern auf die Verteilung der Verantwortung für bestimmte Aktionen und Operationen zwischen Objekten:
- Der Ersteller ist für die Erstellung von Objekten verantwortlich.
- Der Controller ist für die Vorgänge der Benutzer verantwortlich
- Die Indirektion ist für die Organisation einer schwachen Vernetzung zwischen Objekten verantwortlich.
Usw.
Im Zusammenhang mit diesem Artikel möchte ich mich auf folgende Muster konzentrieren:
- Informationsexperte
- Reine Herstellung
Problem: Was ist das Grundprinzip der Aufteilung der Verantwortlichkeiten zwischen Einrichtungen?
Lösung: Weisen Sie diese Aufgabe einer Klasse zu, die über ausreichende Informationen verfügt, um sie auszuführen.
Mit anderen Worten:
Die Verantwortung sollte demjenigen übertragen werden, der die maximal erforderlichen Informationen für die Ausführung besitzt - dem Informationsexperten.
Im Kontext von C # : sollte eine Methode in der Klasse deklariert werden, deren Felder und Eigenschaften in dieser Methode verwendet werden.
Wenn zum Beispiel zur Berechnung des Schuldenbetrags alle erforderlichen Informationen im Wesen des Schuldners liegen (Betrag, Zeitpunkt des Darlehens), sollte in diesem Wesen die geeignete Methode angekündigt werden.
Natürlich ist diese Vereinfachung etwas übertrieben, aber der Hauptpunkt sollte klar sein.
Reine Herstellung
Problem: Welche Klasse sollte die Implementierung von hoher Kohäsion und niedriger Kopplung bereitstellen, wenn die Information Expert-Vorlage keine geeignete Lösung bietet?
Lösung: Weisen Sie einer künstlichen Klasse, die kein spezifisches Konzept des Themenbereichs darstellt, eine Gruppe von Aufgaben mit einem hohen Maß an Engagement zu.
Mit anderen Worten:
Wenn es nicht möglich ist, die geeignete Entität des Subjektmodells, der die Verantwortung zugewiesen werden könnte, eindeutig auszuwählen, muss eine synthetische Klasse erstellt werden, die im Subjektbereich nicht vorhanden ist.
Im Kontext von C # : Wenn die Implementierung der Methode die einheitliche Verwendung mehrerer Entitäten oder externer Abhängigkeiten vorsieht, sollte diese Methode in einer separaten Klasse deklariert werden. Und diese Klassen werden Dienste genannt.
Wenn Sie beispielsweise zur Berechnung des Schuldenbetrags nicht nur die Attribute des Schuldners, sondern auch die Parameter der Bank (Zinssatz, zulässige Verzögerungszeit) kennen müssen, lohnt es sich, einen separaten Dienst mit der entsprechenden Methode zu erstellen.
Und was bedeutet das alles?
Im Zusammenhang mit dem Vorstehenden können wir einen ziemlich einfachen Algorithmus unterscheiden, der hilft, eine Entscheidung zu treffen, die Methode in ein Modell oder in einen Dienst zu stellen :
- Wenn eine Methode nur Modelleigenschaften verwendet, sollte sie im Modell deklariert werden
- Wenn eine Methode zum größten Teil die Eigenschaften eines Modells und nur teilweise eines anderen Modells verwendet, kann sie im Modell deklariert werden
- Wenn die Methode die Eigenschaften mehrerer Modelle einheitlich verwendet, lohnt es sich, sie in einen separaten oder vorhandenen Dienst zu verschieben
- Wenn eine Methode eine externe Abhängigkeit verwendet, z. B. ein Repository, sollte sie in einen separaten oder vorhandenen Dienst verschoben werden
Zusammenfassung
GRASP-Muster können nicht als die ultimative Wahrheit angesehen werden. Sie können jedoch dazu beitragen, leicht zu verstehen, welchem Objekt dieses oder jenes Verhalten zugewiesen werden soll. Dementsprechend ist der Streit anämisch oder ein „reiches“ Modell möglicherweise nicht sinnvoll, da die Entscheidung in jedem Fall in Abhängigkeit vom Modell des Themenbereichs und dem damit verbundenen Verhalten getroffen wird. Während des Systemdesigns werden sowohl Entitäten mit als auch ohne Verhalten angezeigt. Und das ist ganz richtig.