Una de las claves para desarrollar código mantenible en programación orientada a objetos es la correcta encapsulación de las lógicas en las clases que modelan nuestros conceptos de dominio.

Por ejemplo, consideremos el siguiente caso de uso, en el que queremos registrar un usuario en nuestra aplicación:

Aquí estamos haciendo la validación del email a nivel de caso de uso. Eso quiere decir que en cada caso de uso donde tengamos que tener en cuenta esta lógica deberemos de replicarla, con el peligro de que en algún caso se nos olvide. O incluso de que, cuando en el futuro se modifique esta lógica, hayamos de cambiarla en todos los puntos donde se aplica.

Un primer paso que podemos dar para mejorar esto es encapsular la validación dentro de la clase User para que en todos los casos de uso (como registrar usuario, editar usuario...) esta validación se haga automáticamente de manera interna.

Parece que este debería ser el patrón a aplicar. Sin embargo, ahora desde producto nos piden que los usuarios pueden tener una agenda de contactos con un nuevo caso de uso:

¿Qué podemos hacer ahora? Como desarrollador se me ocurren muchos parches que podría generar esto, desde duplicar el método isValid hasta hacerlo público en la clase User y encontrarnos aberraciones del estilo

(y no disimuléis, que no os sorprende tanto).

Está claro que nos hemos encontrado con un escollo que no nos permite avanzar tal y como quisiéramos. Algo que parece que nos obliga a escribir código feo y sin sentido. Está claro que esto nos indica que algo hemos hecho mal. Yo tengo la siguiente máxima:

Si tu modelo describe adecuadamente el problema que quieres resolver, el código encajará y fluirá a la perfección. Por otro lado, si te encuentras con que tienes que hacer trampas en el código para desarrollar tu aplicación, esto es un indicador de que tus abstracciones deben ser revisadas.

En este caso, el problema se origina porque estamos tratando de atribuir propiedades al concepto email sin haber creado una abstracción que lo represente. Email es un concepto que no depende de Usuario ni tampoco de Contacto, sino que tiene sentido por sí mismo. Tiene datos (su propio valor) y comportamiento (sabe si es válido o no), por lo tanto se merece una clase.

Notemos que hemos hecho el campo value inmutable con la keyword final. Esto es debido a una característica definitoria del patrón que estamos usando. Se trata del patrón Value-Object. Un value-object es una clase que representa un modelo, como lo hacen las entidades, pero a diferencia de estas, los value-objects no tienen una identidad propia que les distingue del resto, sino que vienen definidos exclusivamente por, precisamente, su valor.

Por ejemplo, una persona viene definida por una identidad propia. Si esa persona se cambia de ropa, se muda, se tiñe el pelo, se cambia de nombre o incluso de sexo, seguirá siendo la misma persona, pero con una serie de propiedades diferentes a antes de ese cambio. Normalmente al modelar una persona (o cualquier otra entidad) en una aplicación representamos esta identidad única con un identificador único (un número o una cadena de caracteres).

Sin embargo, una dirección de email, una dirección de correo, un teléfono… son conceptos que vienen definidos por los propios datos que representan. Si estos datos cambiaran, los mismos conceptos serían diferentes. Si comparamos la dirección de correo user@mail.com y hello@company.com no diremos que son la misma donde se han cambiado algunos datos entre una y otra. Incluso propiedades con múltiples valores, como por ejemplo

No diremos que la dirección “Calle Mayor, 1” es la misma que “Plaza Central, 1” o que “Calle Mayor, 5”. Todo esto deriva en que no tiene sentido modificar los campos de un value-object, porque entonces pasa a ser otro value-object diferente, por lo tanto

Los value-objects han de ser modelos inmutables.

Otras ventajas que nos aportan los value-objects son:

Por ejemplo, consideremos la siguiente clase de Contacto:

Cada vez que tengamos que instanciar un contacto usando el constructor tendremos que tener mucho cuidado de poner la string correcta en la posición adecuada. Comparémoslo con:

Como conclusión, no te limites a modelar tu dominio usando simplemente entidades. Llenar tu codebase de estas sencillas clases te proporcionará unas ventajas increíbles a la hora de aplicar adecuadamente la lógica de tu negocio.

David Pravos — Tech Lead @LambdaLoopers

We are the team that thinks, works and aims to ensure you a successful digital journey. 👨🏻‍💻👩🏻‍💻 #webdevelopment #digital #technology

We are the team that thinks, works and aims to ensure you a successful digital journey. 👨🏻‍💻👩🏻‍💻 #webdevelopment #digital #technology