Éviter les objets de domaine gonflés

12

Nous essayons de déplacer les données de notre couche de service gonflée vers notre couche de domaine en utilisant une approche DDD. Nous avons actuellement beaucoup de logique commerciale dans nos services, qui est répartie partout et ne bénéficie pas de l'héritage.

Nous avons une classe de domaine centrale qui est au centre de la plupart de nos travaux - un métier. L'objet Trade saura se tarifier, estimer le risque, se valider, etc. On pourra alors remplacer les conditionnels par du polymorphisme. Par exemple: SimpleTrade se tarifiera dans un sens, mais ComplexTrade se tarifera dans une autre.

Cependant, nous craignons que cela ne fasse gonfler les classes commerciales. Il devrait vraiment être en charge de son propre traitement, mais la taille de la classe va augmenter de façon exponentielle à mesure que de nouvelles fonctionnalités sont ajoutées.

Nous avons donc des choix:

  1. Mettez la logique de traitement dans la classe Trade. La logique de traitement est désormais polymorphe en fonction du type de transaction, mais la classe de transaction a désormais plusieurs responsabilités (prix, risque, etc.) et est grande
  2. Mettez la logique de traitement dans une autre classe telle que TradePricingService. N'est plus polymorphe avec l'arbre d'héritage Trade, mais les classes sont plus petites et plus faciles à tester.

Quelle serait l'approche suggérée?

djcredo
la source
Pas de problème - je suis heureux d'accepter la migration!
1
Méfiez-vous du contraire: martinfowler.com/bliki/AnemicDomainModel.html
TrueWill
1
"la taille de la classe va augmenter de façon exponentielle à mesure que de nouvelles fonctionnalités sont ajoutées" - tout programmeur devrait savoir mieux que de mal utiliser le mot "exponentiellement" comme ça.
Michael Borgwardt
@Piskvor c'est juste stupide
Arnis Lapsa
@Arnis L .: Merci pour votre commentaire réfléchi. Notez que c'était, je cite, "migré de stackoverflow.com le 22 novembre à 22:19", avec mon commentaire. J'ai maintenant supprimé mon commentaire selon lequel "ce serait mieux chez programmers.SE"; maintenant, avez-vous quelque chose à ajouter, ou était-ce la seule idée que vous vouliez exprimer?
Piskvor a quitté le bâtiment

Réponses:

8

Si vous utilisez le domaine, envisagez de traiter votre classe de commerce comme une racine agrégée et répartissez ses responsabilités dans d'autres classes.

Vous ne voulez pas vous retrouver avec une sous-classe de commerce pour chaque combinaison de prix et de risque, donc un commerce peut contenir des objets de prix et de risque (composition). Les objets Prix et Risque effectuent les calculs réels, mais ne sont visibles par aucune classe à l'exception du Commerce. Vous pouvez réduire la taille du commerce, sans exposer vos nouvelles classes au monde extérieur.

Essayez d'utiliser la composition pour éviter les grands arbres d'héritage. Trop d'héritage peut conduire à des situations où vous essayez de chausse-pied dans un comportement qui ne correspond pas vraiment au modèle. Il vaut mieux retirer ces responsabilités dans une nouvelle classe.

Terry Wilcox
la source
Je suis d'accord. Penser les objets en termes de comportements qui constituent la solution (Pricing, RiskAssessment) plutôt que d'essayer de modéliser le problème évite ces classes monolithiques.
Garrett Hall
Aussi d'accord. Composition, beaucoup de classes plus petites avec des responsabilités spécifiques et uniques, utilisation limitée de méthodes privées, beaucoup d'interfaces heureuses, etc.
Ian
4

Votre question me fait définitivement penser au modèle de stratégie . Ensuite, vous pouvez échanger dans diverses stratégies de négociation / tarification, similaires à ce que vous appelez un TradePricingService.

Je pense vraiment que le conseil que vous obtiendrez ici est d'utiliser la composition plutôt que l'héritage.

Scott Whitlock
la source
2

Une solution possible que j'ai utilisée dans un cas similaire est le modèle de conception de l' adaptateur (la page référencée contient de nombreux exemples de code). Peut-être en combinaison avec le modèle de conception de délégation pour un accès facile aux principales méthodes.

Fondamentalement, vous divisez la fonctionnalité Trader en un certain nombre de domaines disjoints - par exemple, la gestion des prix, les risques, la validation peuvent tous être différents. Pour chaque zone, vous pouvez ensuite implémenter une hiérarchie de classes distincte qui gère cette fonctionnalité exacte dans les différentes variantes nécessaires - toutes une interface commune pour chaque zone. La classe Trader principale est ensuite réduite aux données et références les plus élémentaires à un certain nombre d'objets de gestionnaire, qui peuvent être construits en cas de besoin. Comme

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

Un avantage majeur de cette approche est que les combinaisons possibles, par exemple de prix et de ricks, sont complètement séparées et peuvent ainsi être combinées selon les besoins. C'est assez difficile avec l'héritage monothread de la plupart des langages de programmation. La décision de la combinaison à utiliser peut même être calculée très tard :-)

J'essaie généralement de garder les classes d'adaptateurs - par exemple les sous-classes IPriceCalculatorci-dessus - sans état. C'est-à-dire que ces classes ne doivent pas contenir de données locales si possible, afin de réduire le nombre d'instances à créer. Donc, je fournis généralement l'objet adapté principal comme argument dans toutes les méthodes - comme getPrice(ITrader)ci-dessus.

Tonny Madsen
la source
2

ne peut pas dire grand-chose sur votre domaine, mais

Nous avons une classe de domaine centrale qui est au centre de la plupart de nos travaux - un métier.

... c'est une odeur pour moi. J'essaierais probablement d'esquisser les différentes responsabilités de la classe et éventuellement de la décomposer en différents agrégats. Les agrégats seraient ensuite conçus en fonction des rôles et / ou des points de vue des parties prenantes / experts du domaine impliqués. Si le prix et le risque sont impliqués dans le même comportement / cas d'utilisation, ils appartiendraient probablement au même agrégat. Mais s'ils sont découplés, ils peuvent appartenir à des agrégats distincts.

Peut -être que RiskEvaluation pourrait être une entité distincte dans votre domaine, éventuellement avec un cycle de vie spécifique (je ne peux pas vraiment dire, je spécule simplement ... vous connaissez votre domaine, je ne sais pas), mais la clé est de faire des concepts implicites explicite et pour éviter un couplage qui n'est pas déterminé par le comportement, mais uniquement par le couplage de données hérité.

En général, je pense au comportement attendu et aux différents cycles de vie des composants impliqués. Le simple fait d'ajouter un comportement au-dessus des données groupées crée des objets gonflés. Mais les données ont été regroupées selon une conception basée sur les données existante, il n'est donc pas nécessaire de s'y tenir.

ZioBrando
la source