13 abril 2010

Composición

Hace unos años atrás cuando dictaba cursos sobre POO y Java, di un ejercicio que consistía en diseñar el "checkout" de un Supermercado con soporte para ofertas del estilo "lleve 2 y pague 1".
La intención del ejercicio era que los alumnos piensen en como utilizar objetos para lidiar con la complejidad de aplicar las diferentes ofertas.
Esperaba también que se dieran cuenta que el precio de un producto depende de factores como: la fecha, el "mercado", ofertas u estrategias de negocio.
Imagine que comenzarían planteando la clase Producto con un "estilo base de datos":


Y que a lo largo del ejercicio iban a llegar a una especie de "lista de precios":


Sin embargo no fue como esperaba. Para uno de los alumnos la expresión "el producto tiene un precio" significaba que el un producto tenia que responder directamente su precio. Trate de justificar mi posición desde distintos puntos de vista, pero luego abandone la discusión.

Nota al margen:
Mi experiencia dando clases me enseño que es mejor no entrar en discusiones sin salida. Es preferible escuchar pacientemente y expresar cuales son los problemas del planteo desde distintos puntos de vista, con suerte la discusión evoluciona y se aprende mucho en el proceso. Pero si la discusión se estanca, es preferible abandonarla.

Escribo sobre este ejemplo después de tanto tiempo, por que veo que el problema de como expresar la relación de composición se repite en otros contextos.
Por ejemplo ¿Cómo modelarían que "un cliente que tiene contactos"? Una forma de hacerlo es:



Sin embargo, al hacer la traducción literal de este diagrama de clases, el modelo esta diciendo: "el cliente esta compuesto de contactos". La diferencia parece sutil, pero a medida que el sistema crece tiene consecuencias importantes:
  • Agregar nuevos contactos implica modificar la instancia de cliente y trae consecuencias en la forma de asegurar que se cumplan las reglas de negocio, por ejemplo: que un cliente cambie su numero CUIT es raro y puede afectar a los sistemas de facturación; pero es esperable que sus contactos cambien seguido sin mayores consecuencias para el resto del sistema, entonces hay que distinguir entre "modificaciones pesadas" y "modificaciones livianas".
  • Transmitir el cliente por "el cable" implica transmitir sus contactos, y como esta información puede ser pesada uno tiene que usar alguna estrategia como un objeto que represente una vista del cliente sin sus contactos (DTO), o bien hacer que la lista de contactos se cargue por demanda (lazy load).

Nota al margen: Creo que uno de los causantes de confusión es el uso del verbo tener. En nuestra manera de expresarnos es natural decir "el producto tiene un precio", sin embargo el "tiene" de nuestro lenguaje no expresa necesariamente una relación de composición. Erich Fromm, escribió un libro muy bueno llamado "Tener o Ser" que habla sobre la influencia del "tener" en la sociedad. Es curioso como el consumismo cambio nuestras formas de expresión, por ejemplo en lugar de decir "siento un dolor de cabeza" decimos "tengo un dolor de cabeza".

En cierto sentido el problema es similar al de Producto-Precio, una alternativa para expresar la relación del cliente con sus contactos es tener una "agenda de contactos" mediante la cual se pueden encontrar los contactos para un cliente:



¿Qué modelo expresa mejor la realidad?

Creo que pensar que un modelo es correcto por que expresa "la realidad" mejor que otro, sufre del problema de dar por sentado que nuestra percepción y traducción al sistema es absolutamente correcta: para uno de mis alumnos la realidad que "el producto tiene un precio", se traducía correctamente en "unProducto.getPrecio()".

Por eso es necesario tener en cuenta cuestiones que afectan a la percepción y traducción del problema:
  • El verbo "tener" tiene un uso extremadamente ambiguo en nuestra cultura, por ejemplo es más adecuado decir que a un producto se le asigna un precio y quizás de esa forma ya vemos el dominio con otra perspectiva.
  • Los "ritmos" de cambio pueden hacer que sea más conveniente pensar en objetos distintos. Por ejemplo el precio varia con el tiempo y el entorno, pero el producto no; algo similar ocurre con el cliente y sus contactos.
    Separar las cosas que cambian a distintas velocidades ayuda a evitar side effects innecesarios y simplifica la aplicación de reglas de negocio.
  • Ver si existe en el dominio un intermediario en la relación, como por ejemplo una "lista de precios". Es claro que el software es un medio distinto: la lista de precio existe físicamente por que es una herramienta para definir y recordar el precio de un producto, y ese rol puede cumplirse en software sin necesidad de tener una "lista de precios" propiamente dicha. Sin embargo explorar esas analogías puede servir para ver conceptos del dominio.

2 comentarios:

  1. Diego, muy bueno el post. Me gusta el concepto de "ritmos" de cambio que ya por ahí había leído de Beck, pero están muy buenos tus ejemplos y la forma en que los explicás. De todas formas lo que más me hace ruido del diseño propuesto es estar pasando una estructura de datos producto a una función calcularPrecio. Me suena tan a viejo paradigma.

    ResponderEliminar
  2. Hernan, gracias por comentar :)

    La idea de ritmos, creo que la leí del libro de "Design Patterns" (tambien me acuerdo haberla leido en la descripcion del anti-pattern Big Ball of Mud). La introducción de ese libro es muy buena, solo que todo el mundo se la salta para leer la referencia de los patterns :P

    A mi también me hacia ruido la "función" calcularPrecio, creo que como todo en diseño el secreto esta en balancear y darse cuenta que comportamiento va en cada objeto (en ese caso cada problema es unico, y es dificil para mi generalizar "una" forma de hacer las cosas).

    Dejo de hacerme ruido cuando empece a plantearme
    ¿Cuales son las alternativas?
    - Hacer que producto sepa su precio (tiene la contra que escribi en el post: el precio varia con el entorno, pero el producto no).
    - Tener un objeto Producto y uno ProductoValuado (o algo asi) que responda el precio que le asignaron y que sea polimorfico a la interfaz de Producto.

    Esa ultima opcion quizas convenga en algunos casos -por ejemplo para hacer la UI-, pero para poder construir ProductoValuado vas a terminar enviando un mensaje "calcularPrecio" a alguna especie de "lista de precio".

    ResponderEliminar