La herencia mal planteada

En cuestiones de ORM (Object Relational Mapping) casi siempre asociamos una relación uno a uno (1:1) en bases de datos relacionales, con una herencia de clases. Esquematizar este tipo de diseño puede llevarnos a futuros errores cuando nos planteamos la arquitectura de cualquier proyecto.

Vamos a ver ejemplos que nos permitan entender dónde está la trampa.

Si tratamos de representar la estructura de mascotas en un diagrama de clases, pudiéramos pensar que todas tienen un dueño, al igual que todas tienen un nombre y una fecha de nacimiento. De ahí, y derivando en acciones más específicas, vemos que algunas mascotas se pueden acariciar (perro, gato), y otras no tanto (por ejemplo una serpiente, sí sí, una serpiente, que dueños locos se sobran en este mundo).

Representando esto, tendríamos una clase mascota, que tendría una propiedad persona (dueño), otra propiedad fecha de nacimiento, y todas aquellas que entendamos pueden ser comunes a todas las mascotas. Luego, cada mascota, heredaría su funcionalidad e implementaría sus funciones más específicas (no quiero complicar el diagrama porque al final no es el objetivo de este artículo)

Este diagrama, traducido a una base de datos relacional, nos quedaría con tres tablas, mascota, perro y serpiente, donde tendríamos una relación de uno a uno entre ellas, y cualquier ORM decente, sabría que leer un perro implica leer la información asociada al mismo en la tabla mascota, y lo mismo ocurriría si tratamos de insertar o actualizar cualquiera de las clases que implementen mascota.

Pero…  (Siempre hay un pero)

Si analizamos esto mismo con alumnos y maestros, ¿qué pasaría? Vamos allá…

Un maestro, al igual que un alumno, son personas, ambos tienen un nombre, apellidos, padres, edad, etc. Mientras más elementos en común tengan varias clase, más confirmamos la presencia de una herencia, así que si llevamos esto a un diagrama de clases, nos quedaría de la siguiente forma:

Visto desde el punto de vista de bases de datos, igual caeríamos en tres tablas, con una relación uno a uno entre ellas (persona, maestro y alumno) y todo perfecto con el ORM. Todo perfecto hasta que a un maestro se le ocurre estudiar. Plantearnos esta situación nos crea un problema, ¿cómo digo yo que un maestro también es un alumno? ¿Cómo puedo asumir que una misma persona ahora es estudiante y maestro?

La encapsulación de la propia herencia no lo permite, yo no podría reutilizar la información de persona ahora en alumno, por lo que aquí la herencia no aplica y, la solución pasa por convertir la herencia, en una asociación de Persona tanto en la clase Maestro como en la clase Alumno, o como una alternativa, pudiéramos definir una lista de responsabilidades asociadas a una persona, de manera que pudiera ocupar la función de alumno y maestro al mismo tiempo.

¿Cómo podemos identificar este tipo de problema cuando nos enfrentamos a un diseño de cualquier aplicación?

Roles que definen responsabilidad

Los roles son actividades que pueden ser compartidas por un elemento de nuestro diseño, siempre que exista un mismo objeto que pueda compartir más de un rol, la herencia no es la solución. Es por esto que en el análisis de mascotas esto no ocurre, ya que un perro no podrá ser nunca una serpiente y tampoco viceversa, al menos hasta hoy 😉

Fabio Maulo, team leader de para mí el mejor ORM que existe hoy en día, NHibernate, hace mucho tiempo hacía una historia sobre un personaje que vive en las colinas de Italia y que ejemplificaba este problema con la herencia.

Salu2

7 comentarios en “La herencia mal planteada”

  1. Chapeau…

    Aunque no hay que irse a ORMs para ver este fallo de diseño: es uno de los más comunes que yo me encuentro, y creo que se debe mucho a como se explica en ciertos lugares la programación orientada a objetos.

    Un saludo!!!!

  2. @Eduard, tienes toda la razón… no es un problema relacionado solo con ORMs, sino de todo el concepto de OOP. De todas maneras yo quise irme por los ORM porque uno de los caminos que llevan a este error, es cuando pensamos que toda relación que un ORM representa 1:1, termina siendo en nuestro diseño una herencia.. gracias por el comentario…

  3. @Eduardo y @Omar: Si yo quiero clavar un clavo con un destornillador y tengo problemas, la culpa no es ni del clavo ni del destornillador. El problema es de diseño (la parte humana del desarrollo), así que no creo que los ORM ni la OOP sean responsables.

    Si el error que mencionas es común -y sí, lo es- es -creo- en buena medida debido a esa mala tendencia que tenemos los programadores de poner las necesidades de las herramientas que usamos por encima de la lógica del negocio. En el ejemplo, estamos analizando cómo se relaciona la base de datos con las clases que la representan y se nos olvida analizar qué tan bien representan esas clases la realidad del negocio.

    El error es casi metodológico. Las clases deben representar un negocio, y cómo se graban en la BBDD es algo que no debería condicionarme a la hora de ese análisis. Los ORM deberían ayudarme a almacenar mi representación del negocio -condicionada exclusivamente por la realidad de ese negocio- en un repositorio persistente -que tiene sus propias necesidades, pero que deben ajustarse a la realidad del negocio, que es la única que manda-… los problemas empiezan cuando invertimos los roles, intentando convertirnos en herramientas y dejar que las herramientas diseñen por nosotros.

  4. @Andrés
    Por supuesto!!!! Si de mi comentario se intuía que el problema era la OOP… ni mucho menos. Yo decía que, para mi, una de las causas es “como se enseña la OOP en ciertos sitios”, no de la OOP en si misma!

    Secundo totalmente tu comentario, yo sólo queria apuntar el hecho de que _este_ problema concreto que mencionaba Omar es uno de los fallos de diseño más comunes (o que al menos yo he visto más veces) en cualquier tipo de desarrollo (con o sin ORMs).

    Un abrazo!

  5. Yo pienso lo mismo.. se trata de un problema académico.. hoy en día con DDD, ninguna técnica de desarrollo se debería de anteponer a las necesidades de nuestro dominio..

    Pero pasa, por esquemáticos, por un mal concepto, o por otras causas.. este problema pasa. En mi trabajo anterior se dio el caso con -persons, usuarios y otro rol que compartía información con persona, llamado aprobadores- se usaba Linq2sql y no se implementó la herencia.. pero de estar usando un ORM que lo permitiera, se hubiese cometido este error. (de aquí saqué el artículo)

    Son muchos los caminos que nos llevan a caer en algo así, incluso sin pensar en un ORM o en bases de datos…

    Gracias por los comentarios 😉

Deja un comentario

Tu dirección de correo electrónico no será publicada. Los campos obligatorios están marcados con *