Vermeiden aufgeblähter Domänenobjekte

12

Wir versuchen, mithilfe eines DDD-Ansatzes Daten aus unserer aufgeblähten Service-Schicht in unsere Domain-Schicht zu verschieben. Wir haben derzeit eine Menge Geschäftslogik in unseren Diensten, die über den gesamten Ort verteilt ist und nicht von der Vererbung profitiert.

Wir haben eine zentrale Domain-Klasse, auf die sich der Großteil unserer Arbeit konzentriert - einen Trade. Das Handelsobjekt weiß, wie es sich selbst bewertet, wie es das Risiko einschätzt, sich selbst validiert usw. Wir können dann die Bedingungen durch Polymorphie ersetzen. ZB: SimpleTrade wird sich auf die eine Weise selbst bewerten, ComplexTrade wird sich auf die andere Weise bewerten.

Wir sind jedoch besorgt, dass dies die Trade-Klasse (n) aufblähen wird. Es sollte eigentlich für seine eigene Verarbeitung verantwortlich sein, aber die Klassengröße wird exponentiell zunehmen, wenn mehr Funktionen hinzugefügt werden.

Wir haben also die Wahl:

  1. Setzen Sie die Verarbeitungslogik in die Klasse Trade. Die Verarbeitungslogik ist jetzt polymorph, basierend auf der Art des Handels, aber die Handelsklasse hat jetzt mehrere Verantwortlichkeiten (Preisgestaltung, Risiko usw.) und ist groß
  2. Fügen Sie die Verarbeitungslogik in eine andere Klasse wie TradePricingService ein. Mit dem Trade-Vererbungsbaum nicht mehr polymorph, aber Klassen sind kleiner und einfacher zu testen.

Was wäre der vorgeschlagene Ansatz?

djcredo
quelle
Kein Problem - ich akzeptiere gerne die Migration!
1
Vorsicht vor dem Gegenteil: martinfowler.com/bliki/AnemicDomainModel.html
TrueWill
1
"Die Klassengröße wird exponentiell zunehmen, wenn mehr Funktionen hinzugefügt werden" - jeder Programmierer sollte es besser wissen, als das Wort "exponentiell" so zu missbrauchen.
Michael Borgwardt
@ Piskvor, das ist nur dumm
Arnis Lapsa
@Arnis L .: Vielen Dank für Ihren nachdenklichen Kommentar. Beachten Sie, dass dies, Zitat, "migriert von stackoverflow.com 22. November um 22:19", zusammen mit meinem Kommentar von dort. Ich habe jetzt meinen Kommentar entfernt, dass "dies bei Programmierern besser wäre. SE"; Haben Sie noch etwas hinzuzufügen, oder war das die einzige Idee, die Sie zum Ausdruck bringen wollten?
Piskvor hat das Gebäude

Antworten:

8

Wenn Sie domänenorientiert arbeiten, sollten Sie Ihre Trade-Klasse als Gesamtwurzel betrachten und ihre Verantwortlichkeiten in andere Klassen aufteilen.

Sie möchten nicht für jede Kombination aus Preis und Risiko eine Trade-Unterklasse erhalten, daher kann ein Trade Preis- und Risikoobjekte (Zusammensetzung) enthalten. Die Objekte Price und Risk führen die eigentlichen Berechnungen durch, sind jedoch für keine Klasse außer Trade sichtbar. Sie können die Größe des Handels reduzieren, ohne Ihre neuen Klassen der Außenwelt auszusetzen.

Verwenden Sie die Komposition, um große Vererbungsbäume zu vermeiden. Zu viel Vererbung kann zu Situationen führen, in denen Sie versuchen, ein Verhalten zu erkennen, das nicht wirklich zum Modell passt. Es ist besser, diese Verantwortlichkeiten einer neuen Klasse zuzuordnen.

Terry Wilcox
quelle
Genau. Das Denken an Objekte in Bezug auf Verhaltensweisen, die die Lösung bilden (Pricing, RiskAssessment), anstatt zu versuchen, das Problem zu modellieren, vermeidet diese monolithischen Klassen.
Garrett Hall,
Stimme auch zu. Zusammensetzung, viele kleinere Klassen mit spezifischen, einzelnen Verantwortlichkeiten, begrenzte Verwendung von privaten Methoden, viele glückliche Schnittstellen, etc.
Ian
4

Ihre Frage lässt mich definitiv an das Strategiemuster denken . Dann können Sie verschiedene Handels- / Preisstrategien austauschen, ähnlich wie Sie es bei einem nennen TradePricingService.

Ich denke auf jeden Fall, dass der Rat, den Sie hier erhalten, darin besteht, Komposition anstelle von Vererbung zu verwenden.

Scott Whitlock
quelle
2

Eine mögliche Lösung, die ich in einem ähnlichen Fall verwendet habe, ist das Adapterdesignmuster (die referenzierte Seite enthält viele Beispielcodes). Möglicherweise in Kombination mit dem Delegationsentwurfsmuster für den einfachen Zugriff auf die Hauptmethoden.

Grundsätzlich unterteilen Sie die Trader-Funktionalität in eine Reihe nicht zusammenhängender Bereiche - z. B. Umgang mit Preisen, Risiken, Validierung - alle können unterschiedliche Bereiche sein. Für jeden Bereich können Sie dann eine separate Klassenhierarchie implementieren, die die genaue Funktionalität in den verschiedenen benötigten Varianten verwaltet - alles eine gemeinsame Schnittstelle für jeden Bereich. Die Trader-Hauptklasse wird dann auf die grundlegendsten Daten und Verweise auf eine Reihe von Handler-Objekten reduziert, die bei Bedarf erstellt werden können. Mögen

interface IPriceCalculator {
  double getPrice(ITrader t);
}
interface ITrader {
  IPriceCalculator getPriceCalculator();
}
class Tracer implements ITrader {
  private IPriceCalculator myPriceCalculator = null;
  IPriceCalculator getPriceCalculator() {
    if (myPriceCalculator == null)
      myPriceCalculator = PriceCalculatorFactory.get(this);
    return myPriceCalculator;
  }
}

Ein wesentlicher Vorteil dieses Ansatzes besteht darin, dass die möglichen Kombinationen von z. B. Preisen und Ricks vollständig voneinander getrennt sind und somit nach Bedarf kombiniert werden können. Dies ist mit der Single-Thread-Vererbung der meisten Programmiersprachen ziemlich schwierig. Die Entscheidung, welche Kombination verwendet werden soll, kann sogar sehr spät berechnet werden :-)

Normalerweise versuche ich, die Adapterklassen - zB die Unterklassen von IPriceCalculatoroben - zustandslos zu halten. Dh diese Klassen sollten möglichst keine lokalen Daten enthalten, um die Anzahl der zu erstellenden Instanzen zu reduzieren. Daher gebe ich normalerweise das angepasste Hauptobjekt als Argument in allen Methoden an - wie getPrice(ITrader)oben.

Tonny Madsen
quelle
2

Ich kann nicht viel über Ihre Domain sagen, aber

Wir haben eine zentrale Domain-Klasse, auf die sich der Großteil unserer Arbeit konzentriert - einen Trade.

... es ist ein Geruch für mich. Ich würde wahrscheinlich versuchen, die verschiedenen Verantwortlichkeiten der Klasse zu skizzieren und sie schließlich in verschiedene Aggregate zu zerlegen. Aggregate würden dann nach Rollen und / oder Standpunkten der beteiligten Stakeholder / Domain-Experten entworfen. Wenn Preis und Risiko am selben Verhalten / Anwendungsfall beteiligt sind, gehören sie wahrscheinlich zum selben Aggregat. Wenn sie jedoch entkoppelt sind, gehören sie möglicherweise zu getrennten Aggregaten.

Vielleicht ist RiskEvaluation eine separate Entität in Ihrer Domäne, möglicherweise mit einem bestimmten Lebenszyklus (ich kann nicht wirklich sagen, ich spekuliere nur ... Sie kennen Ihre Domäne, ich weiß nicht), aber der Schlüssel besteht darin, implizite Konzepte zu erstellen explizite und um Kopplungen zu vermeiden, die nicht durch Verhalten, sondern nur durch ältere Datenkopplungen bedingt sind.

Im Allgemeinen würde ich über das erwartete Verhalten und die unterschiedlichen Lebenszyklen der beteiligten Komponenten nachdenken. Durch Hinzufügen von Verhalten zu gruppierten Daten werden aufgeblähte Objekte erstellt. Die Daten wurden jedoch nach einem vorhandenen datengesteuerten Design gruppiert, sodass es nicht erforderlich ist, sich daran zu halten.

ZioBrando
quelle