Como mejorar tu programación: Encapsulación

0


Siempre uno quiere mejorar sus clases para que nuestro código sea mejor y mejoremos profesionalmente. Mi idea es escribir varios artículos con mejoras de código que yo fui viendo a lo largo de los años para que quizás alguno encuentre algún punto de mejora que le interese. 


En esta entrada voy a comentar un poco sobre la encapsulación y cómo uno puede estar generando clases con demasiadas responsabilidades y dominios anémicos.


Encapsulación

La encapsulación es uno de los conceptos fundamentales de la programación orientada a objetos. La idea de la encapsulación es mantener en el objeto su lógica y comportamiento, así como sus propiedades. De esta forma, nos ayuda aislando detalles de implementación del comportamiento expuesto a los clientes.


Si bien es uno de los pilares de OOP es muy frecuente no cumplirla del todo. Pasemos a un ejemplo con una clase Character, a la que en este caso le quiero modificar la vida.


Quiero tener un comportamiento de que si un personaje toma una poción se cura 100 puntos de vida. Una versión más antigua de Lauta podría haber hecho lo siguiente:



Es un camino perfectamente viable. El problema es que estoy rompiendo esta encapsulación de la que hablamos.


 ¿Qué conlleva romper la encapsulación?

Bueno, son varios los motivos pero voy a destacar algunos de los que yo me encontré.


  • Mala organización y duplicación de código
  • Acoplamiento
  • Dominio anémico


Antes de pasar a hablar de estos puntos quiero comentar que descomponer una variable en una propiedad o en funciones Get y Set (GetLife/SetLife) sigue rompiendo el encapsulamiento. Si bien es menos peor que modificar la variable directamente, porque al menos puedo saber dónde se usan los métodos y puedo tener un poco más de control, sigo sin tener el control total de variables y sigo delegando lógica a externos. Esto va a complementar con lo siguiente que voy a comentar.

Mala organización y duplicación de código

Se puede explicar de manera muy sencilla. Supongamos que además de tener una poción que te cura tengo además otro personaje que cumple el mismo propósito, o una acción que cuando tu personaje realiza un objetivo se cura un número de vida. En todos estos ejemplos tendría que duplicar el código en cada una de las acciones. Significa que si, por ejemplo, un día quiero que se cure un poco más o un poco menos tendría que ir por cada una de las acciones a cambiar ese valor.

Por otro lado, con una mala organización nos referimos a que idealmente las variables sólo deberían ser manejadas en el contexto donde existen, en su objeto. De hecho, hay un Code Smell que se refiere a esto llamado Feature Envy. Si nosotros manejamos todo por fuera es muy difícil seguir el rastro de cuándo se llaman las variables, de cómo son modificadas y en qué momento. Es mucho más frecuente a problemas de Side Effects de esta manera.

Acoplamiento

¿Por qué dejamos que cualquier clase que cure maneje la lógica de cómo se cura el personaje? Estamos teniendo más una responsabilidad en esas clases. Esto significa que si yo vengo y cambio cómo funciona la vida va a explotar el código por todos lados. Si separo la vida en dos variables, una con la vida máxima que podría tener y otra con la vida actual, por ejemplo, ya me va a empezar a fallar por todos lados menos en la propia clase que tiene esas variables.


Otro ejemplo podría ser que si tiene menos de 30% de vida cure el doble. De la manera que tenemos organizada el código esto sería una verdadera molestia y terminaríamos teniendo un mismo código duplicado de lógica por todas las clases en vez de converger en un sólo lugar.


En resumen, estoy acoplando el código a cómo se cura, cuando en realidad lo único que le importa es que se cure. El resto de las clases no debería saber cómo se hace.


Dominio anémico

Cuando mencionamos el dominio anémico nos referimos a objetos que tienen muy poca lógica y maneja más que nada datos. Es un anti-patrón que va contra lo que plantea la programación orientada a objetos.


Revisando códigos antiguos, de cuando empezaba a programar, veo que es muy frecuente caer en este problema. Deberíamos de tratar de de siempre tener objetos de dominio con comportamiento, donde sus variables sólo son usadas por la propia clase y el resto interactúa con el objeto mediante los comportamientos que les habilita. De esta forma, la lógica queda en un solo lugar, el resto de clases delegan la responsabilidad del comportamiento al objeto, me queda un dominio mucho más enriquecido, y sobretodo no rompo el encapsulamiento.


Poniendo en común

Entonces, siguiendo los puntos que hablamos, podría refactorear nuestro código agregando un comportamiento a nuestra clase para curarse y que ahí maneje todo lo relacionado a eso. Nos quedaría algo así: 




Ahora sé que cuando llame con una cantidad de puntos de vida lo va a curar. No me hace falta saber cómo lo va a hacer, o si tiene algún limitante o algo que considerar. No es algo que le importe a nadie fuera del objeto, y lo puedo reutilizar en el futuro.


Voy a agregar un ejemplo más para, de paso, detallar un poco sobre los getters y setters. En este ejemplo, quiero que si mi personaje está vivo haga una cosa, y que si está muerto haga otra.


Así como está, estamos rompiendo la encapsulación. Dejamos que la clase externa se encargue de definir la lógica de que un personaje esté muerto o vivo. El propio objeto nunca maneja su estado y lo terminan haciendo fuera por él.

La forma de arreglarlo sería la misma, trasladando esa responsabilidad al objeto:


Nótese que, de esta forma, la lógica nos queda en el objeto de dominio, nos queda reutilizable, nos deja mas desacoplado el resto del código y además es mas legible.

Conclusión

Estas son algunas de las ventajas que tiene no romper la encapsulación, que termina derivando en enriquecer nuestros objetos de dominio. Personalmente, es de las prácticas que aprendí que más me gustan y ayudan. Una vez que dejas de ver ifs acoplados por todo el código, preguntando sobre un valor específico ensuciando todo tu proyecto, para pasar a tener un código más sostenible y legible realmente es difícil volver atrás.

¿Y ustedes? ¿Tuvieron este problema? ¿Se les ocurre alguna otra ventaja que hayan detectado?

Entradas que pueden interesarte

Sin comentarios