Evitar objetos de dominio hinchados

12

Estamos tratando de mover datos de nuestra capa de Servicio hinchada a nuestra capa de Dominio usando un enfoque DDD. Actualmente tenemos mucha lógica de negocios en nuestros servicios, que se extiende por todo el lugar y no se beneficia de la herencia.

Tenemos una clase de dominio central que es el foco de la mayor parte de nuestro trabajo: un comercio. El objeto Trade sabrá cómo valorarse a sí mismo, cómo estimar el riesgo, validarse a sí mismo, etc. Luego, podemos reemplazar los condicionales con polimorfismo. Por ejemplo: SimpleTrade se cotizará de una manera, pero ComplexTrade se cotizará de otra manera.

Sin embargo, nos preocupa que esto hinche las clases de Comercio. Realmente debería estar a cargo de su propio procesamiento, pero el tamaño de la clase aumentará exponencialmente a medida que se agreguen más funciones.

Entonces tenemos opciones:

  1. Ponga la lógica de procesamiento en la clase Trade. La lógica de procesamiento ahora es polimórfica según el tipo de operación, pero la clase de operación ahora tiene múltiples responsabilidades (fijación de precios, riesgo, etc.) y es grande
  2. Ponga la lógica de procesamiento en otra clase, como TradePricingService. Ya no es polimórfico con el árbol de herencia Trade, pero las clases son más pequeñas y más fáciles de probar.

¿Cuál sería el enfoque sugerido?

djcredo
fuente
No hay problema, ¡estoy feliz de aceptar la migración!
1
Cuidado con el reverso: martinfowler.com/bliki/AnemicDomainModel.html
TrueWill
1
"el tamaño de la clase aumentará exponencialmente a medida que se agreguen más funciones" - cualquier programador debería saber mejor que usar mal la palabra "exponencialmente" de esa manera.
Michael Borgwardt
@Piskvor que es simplemente estúpido
Arnis Lapsa
@ Arnis L .: Gracias por su atento comentario. Tenga en cuenta que esto fue, cita, "migró de stackoverflow.com el 22 de noviembre a las 22:19", junto con mi comentario desde allí. Ahora he eliminado mi comentario de que "esto sería mejor para los programadores. SE"; ahora, ¿tiene algo que agregar, o esa fue la única idea que quería expresar?
Piskvor salió del edificio el

Respuestas:

8

Si va a ir impulsado por el dominio, considere tratar su clase Trade como una raíz agregada y divida sus responsabilidades en otras clases.

No desea terminar con una subclase de Comercio para cada combinación de precio y riesgo, por lo que un Comercio puede contener objetos de Precio y Riesgo (composición). Los objetos Precio y Riesgo hacen los cálculos reales, pero no son visibles para ninguna clase, excepto Comercio. Puedes reducir el tamaño del Comercio, sin exponer tus nuevas clases al mundo exterior.

Trate de usar la composición para evitar grandes árboles de herencia. Demasiada herencia puede conducir a situaciones en las que intentas calzar un comportamiento que realmente no se ajusta al modelo. Es mejor llevar esas responsabilidades a una nueva clase.

Terry Wilcox
fuente
Estoy de acuerdo. Pensar en los objetos en términos de comportamientos que forman la solución (precios, evaluación de riesgos) en lugar de tratar de modelar el problema evita estas clases monolíticas.
Garrett Hall
También de acuerdo. Composición, muchas clases más pequeñas con responsabilidades específicas y únicas, uso limitado de métodos privados, muchas interfaces felices, etc.
Ian
4

Su pregunta definitivamente me hace pensar en el Patrón de Estrategia . Luego puede intercambiar varias estrategias de negociación / fijación de precios, similares a lo que está llamando a TradePricingService.

Definitivamente creo que el consejo que obtendrá aquí es usar composición en lugar de herencia.

Scott Whitlock
fuente
2

Una posible solución que he usado en un caso similar es el patrón de diseño del adaptador (la página referenciada contiene muchos códigos de muestra). Posiblemente en combinación con el patrón de diseño de delegación para facilitar el acceso a los métodos principales.

Básicamente, divide la funcionalidad del comerciante en varias áreas disjuntas, por ejemplo, manejo de precios, riesgos, validación, todas podrían ser áreas diferentes. Para cada área, puede implementar una jerarquía de clases separada que maneje esa funcionalidad exacta en las diferentes variantes necesarias, todas una interfaz común para cada área. La clase principal de Trader se reduce a los datos más básicos y referencias a una serie de objetos de controlador, que se pueden construir cuando sea necesario. Me gusta

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

Una ventaja importante de este enfoque es que las posibles combinaciones de, por ejemplo, precios y ricks están completamente separadas y, por lo tanto, se pueden combinar según sea necesario. Esto es bastante difícil con la herencia de un solo subproceso de la mayoría de los lenguajes de programación. La decisión de qué combinación usar puede incluso calcularse muy tarde :-)

Por lo general, trato de mantener las clases de adaptador, por ejemplo, las subclases de IPriceCalculatorarriba, sin estado. Es decir, estas clases no deberían contener ningún dato local si es posible, para reducir el número de instancias que deben crearse. Por lo general, proporciono el objeto principal adaptado como argumento en todos los métodos, como en el getPrice(ITrader)anterior.

Tonny Madsen
fuente
2

no puedo decir mucho sobre tu dominio, pero

Tenemos una clase de dominio central que es el foco de la mayor parte de nuestro trabajo: un comercio.

... es un olor para mí. Probablemente intente esbozar las diferentes responsabilidades de la clase y eventualmente descomponerlo en diferentes agregados. Los agregados se diseñarían en torno a roles y / o puntos de vista de los interesados ​​/ expertos en el dominio involucrados. Si Price y Risk están involucrados en el mismo comportamiento / caso de uso, probablemente pertenecerían al mismo agregado. Pero si están desacoplados, pueden pertenecer a agregados separados.

Tal vez RiskEvaluation podría ser una entidad separada en su dominio, eventualmente con un ciclo de vida específico (realmente no puedo decir, simplemente estoy especulando ... usted conoce su dominio, no lo sé), pero la clave es hacer conceptos implícitos explícito y para evitar el acoplamiento que no está impulsado por el comportamiento, sino solo por el acoplamiento de datos heredados.

En general, pensaría en el comportamiento esperado y los diferentes ciclos de vida de los componentes involucrados. Solo agregar comportamiento sobre los datos agrupados crea objetos hinchados. Pero los datos se han agrupado de acuerdo con un diseño basado en datos existente, por lo que no hay necesidad de atenerse a eso.

ZioBrando
fuente