Publicado originalmente en carlosvigueras.es
Bueno, pues esta entrada os la debía, y vamos a por ella. Intentaré que sea indoloro.
Vamos a empezar por definir que es un tipo de dato primitivo, y así seguro que todos entramos en contexto. Un tipo de dato primitivo, o también llamado tipo de dato elemental es todo aquel que nos proporciona el lenguaje de forma natural y con los que podemos construir estructuras de datos, un claro ejemplo en C#, sería un tipo string, int, char, booleano, float.. etc. Con este tipo de datos, podemos construir Clases, Estructuras, Dtos, Records o lo que se nos tercie.
Bien, una vez que tenemos todos claro que son los tipos de datos primitivos y por qué lo son, pasemos al tema principal, y es que la tercera regla de Object Calisthenics nos dice que hemos de envolver todos los datos de tipo primitivo y vamos a ver por qué es aconsejable, y si, digo aconsejable, ya que no siempre es posible, o beneficioso realizar esta práctica.
A diferencia de cuando os decía que nunca debéis usar el Else, esta vez, en la medida de lo posible trataremos de envolver o encapsular los tipos primitivos.
Volvamos al ejemplo de la clase robot que veíamos en la entrada de object-calisthenics-no-usar-la-palabra-clave-else, en dicha clase recordad que teníamos un método Avanzar(), el cual recibía por parámetro un tipo primitivo int pasos, y este lo validábamos para que no fuese menor que cero, y si lo era devolvíamos una excepción, ya que no podía retroceder, hasta ahí todo bien.
Pues ahora imaginemos que en nuestro negocio el Robot solo puede avanzar un máximo de 100 pasos, y solo puede hacerlo si los pasos pasados son pares, o sea solo podría avanzar 2,4,6,8,10…100 pasos. Aquí ya estaríamos trabajando con tres validaciones diferentes:
- Que pasos no sea menor que cero.
- Que pasos sea par.
- Que pasos no sea mayor que 100.
Pues implementemos las validaciones en el método Avanzar():
public class Robot
{
private int Position = 0;
public int Avanzar(int pasos)
{
if (pasos < 0 || pasos % 2 != 0 || pasos > 100)
{
throw new InvalidPositionException("Posición no válida");
}
return Position += pasos;
}
}
Sencillo, ¿ no ?, hemos concatenado en el if dos validaciones más para que pasos no sea impar, y no sea mayor que 100, como siempre decimos, esto es funcional, no hay ninguno tipo de problema y los pasos del Robot quedarán validados tal y como queríamos.
Pasa el tiempo el Robot sigue con sus andanzas por el mundo y un montón de aventuras que le suceden (que no os voy a contar), y un día nos proponen una nueva funcionalidad, !el robot ahora puede retroceder!, y las validaciones han de ser las mismas, vaya que bien… pasemos a implementarlo, no hay problema, creamos un método Retroceder() y ya está listo:
public class Robot
{
private int Position = 0;
public int Avanzar(int pasos)
{
if (pasos < 0 || pasos % 2 != 0 || pasos > 100)
{
throw new InvalidPositionException("Posición no válida");
}
return Position += pasos;
}
public int Retroceder(int pasos)
{
if (pasos < 0 || pasos % 2 != 0 || pasos > 100)
{
throw new InvalidPositionException("Posición no válida");
}
return Position -= pasos;
}
}
¡Uy! Estas validaciones que hemos introducido en Retroceder() me suenan mucho a las de Avanzar().
¡Pero si son las mismas!
¿Podría refactorizar?
Bueno pues mejor nos deberíamos esperar a que se cumpla la regla del tres, o mejor.. ¿por qué voy a esperar?
Vamos a encapsular ese tipo primitivo que está ahí, que no me gusta nada y veamos como queda y las ventajas que nos ofrece:
public class Robot
{
private int Position = 0;
public int Avanzar(Pasos pasos)
{
return Position += pasos.Avances;
}
public int Retroceder(Pasos pasos)
{
return Position -= pasos.Avances;
}
}
public class Pasos
{
public int Avances { get; }
private Pasos(int avances)
{
Avances = avances;
}
public static Pasos Create(int avances)
{
if (AreInValidAvances(avances))
{
throw new InvalidPositionException("Posición no válida");
}
return new Pasos(avances);
}
private static bool AreInValidAvances(int avances)
{
return avances < 0 || avances % 2 != 0 || avances > 100;
}
}
Pues ya hemos encapsulado, como vemos en el ejemplo de arriba, hemos sustituido el tipo primitivo por un objeto Pasos, el cual en su método Create() implementa sus validaciones llamando a un método AreInvalidAvances() que valida si los avances pasados son o no permitidos por nuestra lógica de negocio, si no lo son lanzarán una excepción que podremos capturar.
No solo hemos refactorizado el código y ya no tenemos que repetirlo dos veces en dos métodos diferentes, si no que le hemos pasado la responsabilidad de validar los avances a quien realmente tiene que tenerla, es responsabilidad de la clase Pasos validar los avances que vaya a realizar el Robot.
Semánticamente esto es mas correcto, de este modo evitamos crear dos Code Smell_s muy famosos, uno es _Shotgun Surgery (del cual también hablaré), y el otro Primitive Obsession, y conseguimos no tener las mismas validaciones repartidas por todo nuestro código a lo largo de toda la aplicación.
Este tipo de prácticas nos obligan a implementar buenos hábitos en la Programación Orientada a Objetos, creando un código de mayor calidad, mas legible y mas limpio.
¿Qué os ha parecido? !Os leo en comentarios!
Top comments (0)