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:
- 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
- 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?
Réponses:
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.
la source
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.
la source
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
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
IPriceCalculator
ci-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 - commegetPrice(ITrader)
ci-dessus.la source
ne peut pas dire grand-chose sur votre domaine, mais
... 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.
la source