Encapsulamiento en programación
El encapsulamiento es el principio fundamental que te va a ayudar a crear código mantenible. Aquí tienes su definición y tres herramientas sencillas que puedes aplicar para encapsular con sentido.
1. Encapsulamiento
Consiste en agrupar una líneas de código fuertemente relacionadas, darles un nombre y encapsularlas en un método o una clase.
El encapsulamiento es el divide y vencerás de la programación.
Al separar tu código en pequeños fragmentos que puedas tratar individualmente, liberarás memoria en tu cabeza.
Imagínatelo como un puzle de 400 piezas: mientras lo montas, estás lidiando con muchas variables, pero una vez montado, lo puedes tratar como un todo. Cuando lo miras una vez montado ya no ves las 400 piezas, sino una única imagen: has creado una abstracción. :)
En el momento en que consigas agrupar varios conceptos en uno (a esto se le llama crear una abstracción) habrás liberado tu cerebro de memoria porque ya no necesitarás recordar el resultado final.
2.Coste del Encapsulamiento
El encapsulamiento tiene un coste que debes conocer:
El encapsulamiento genera más código.
Es importante ser consciente de esta característica porque te puedes encontrar con la siguiente duda:
"Estoy dividiendo el código como me han aconsejado y resulta que ahora tengo más código y más archivos que antes, ¿lo estoy haciendo bien?"
Sí, es el coste inherente al encapsulamiento, es completamente normal y no debe echarte para atrás.
3. Tres Herramientas para Encapsular con Sentido
Te voy a dar tres herramientas que debes tener presentes siempre que encapsules código:
- Usa nombres que revelen la intención
- Pasa pocos parámetros, tres ya son multitud
- Usa abstracciones del mismo nivel
Vamos a verlos uno a uno.
4. Usa nombres que revelen la intención del código encapsulado
Escoge el nombre de tu función de tal modo que describa sus acciones sin necesidad de entrar a ver su código.
Volviendo al ejemplo del puzle, un método con un mal nombre es como un puzle con una manta encima: aunque esté bien hecho, no se ve desde fuera. Tienes que levantar la manta para conocer el contenido.
Todos hemos trabajado en proyectos donde te pasas la mitad del tiempo "levantando mantas", y a nadie le gusta.
Si en cambio pones un buen nombre, ni tú ni tus compañeros perderéis tiempo navegando entre funciones solo para averiguar cómo funcionan.
La cuestión es que escoger un buen nombre es una tarea difícil, tanto que hay un dicho al respecto:
Solo hay dos cosas difíciles en Ciencias de la Computación: la invalidación de caché* y nombrar cosas - Phil Karlton
*La invalidación de caché es conseguir que una vez está cacheado un valor, si este cambia, se actualice de nuevo y se visualice el cambio.
Siendo una tarea tan difícil, no creas que lo vas a conseguir a la primera. Cambia el nombre tantas veces como sea necesario hasta dar con el adecuado.
5. Pasa pocos parámetros a los métodos creados
Cuando lees un método que recibe parámetros necesitas saber qué es cada uno de ellos para comprender qué hace. Por tanto, cuantos menos haya, más fácil será de comprender. EL objetivo sigue siendo el mismo, ahorrar memoria.
Tienes dos opciones para reducir parámetros:
- Opción 1: encapsular varios parámetros en una nueva clase y pasar dicha clase como parámetro.
- Opción 2: dividir el método en otros que reciban menos parámetros. Es decir, hacer encapsulaciones más pequeñas.
En ambos casos se trata de encapsular. :)
Ejemplo para la opción 1:
Imagina un método de búsqueda de contactos. El usuario puede buscar por diferentes criterios, cada nuevo criterio de búsqueda se convierte en un nuevo parámetro del método.
public Contacto BuscarContactoPor( string nombre, string dirección, int estado, int pais, string email, string empresa, string telefono) { // código del método }
Para resolver este problema podemos crear una nueva abstracción que se llame CriteriosDeBusqueda y en ella encapsular los parámetros de búsqueda. Esto nos permite pasar de varios parámetros a uno solo:
// abstracción que representa todos los criterios por los que puede buscar el usuario public class CriteriosDeBusqueda { public string Nombre { get; set; } public string Direccion { get; set; } public int Estado { get; set; } public int Pais { get; set; } public string Email { get; set; } public string Empresa { get; set; } public string Telefono { get; set; } } public Contacto BuscarContactoPor(CriteriosDeBusqueda criterios) // un sólo parámetro { // código del método }
Ejemplo para la opción 2:
Imagina que tienes una clase Usuario a la que le puedes modificar ciertas propiedades. Para ello, creas un método Modificar al que le pasas todos los parámetros posibles:
public class Usuario { // Constructor omitido por brevedad public string Nombre { get; private set; } // fíjate que todas las propiedades son privadas public string Apellidos { get; private set; } public string Email { get; private set; } public string Contraseña { get; private set; } public void Modificar(string nombre, string apellidos, string contraseña, string email) { // validaciones omitidas por brevedad Nombre = nombre; Apellidos = apellidos; Email = email; Contraseña = contraseña; }
Puedes reducir el número de parámetros si en lugar de un único método, creas tres diferentes:
public class Usuario { // propiedades de usuario public void ModificarNombre(string nombre, string apellidos) { // validaciones omitidas por brevedad Nombre = nombre; Apellidos = apellidos; } public void ModificarContraseña(string contraseña) { // validaciones Contraseña = contraseña; } public void CambiarEmail(string email) { // validaciones Email = email; } }
En este ejemplo se ve claramente cómo la encapsulación ha generado más código que la versión previa.
Siempre que hagas este tipo de cambios valora si el beneficio es mayor que el coste.
Compara:
// menos código pero más costoso de leer usuario.Modificar("Juan", "García", "123456", "juan.garcia@mail.com"); // más código pero más fácil de leer / es más explícito y mantenible usuario.ModificarNombre("Juan", "García"); usuario.ModificarContraseña("123456"); usuario.ModificarEmail("juan.garcia@mail.com");
6. Usa abstracciones del mismo nivel dentro de los métodos
Para saber usar esta técnica es preciso definir primero qué es una abstracción en software:
Una abstracción es el resultado de encapsular una serie de variables, propiedades, comportamientos, etc. en un método o clase generando un concepto genérico cuyo objetivo es reducir la complejidad.
Si cogemos piezas de nuestro código y las juntamos de modo que puedan comportarse como una sola, habremos creado una abstracción. Cada nueva abstracción es un puzle montado.
Hasta aquí la definición. Veamos ahora en este ejemplo a qué se refiere con niveles:
A) Camino hacia el árbol y cojo una fruta B) Giro hasta encarar el árbol, mientras la distancia al árbol sea superior a un metro muevo las piernas alternativamente hacia delante, me paro, alzo el brazo derecho, cojo la fruta
Ambas instrucciones finalizan con el mismo resultado, pero la A) se entiende mejor poque su nivel de abstracción es superior.
Veamos cómo sería un método que contiene instrucciones con diferentes niveles de abstraccion:
void CojerFrutaDeArbol() { Girar(); while(1 < _distancia) // este while no tiene el mismo nivel de abstracción que el resto de instrucciones { var piernaAMover = DetectarPiernaQueTocaMover(); MoverHaciDelanteCon(piernaAMover); } Parar(); AlzarElBrazo(); Coger(Fruta); }
Podemos encapsular el while en un nuevo método que tenga el mismo nivel de abstracción que el resto:
// encapsulamos generando una abstracción void Caminar() { while(1 < _distancia) { MoverHaciaDelanteCon(PiernaDerecha); MoverHaciaDelanteCon(PiernaIzquierda); } } // un método con abstracciones del mismo nivel void CojerFrutaDeArbol() { Girar(); Caminar(); Parar(); AlzarElBrazo(); Coger(Fruta); }
Ya tienes disponibles tus primeras tres herramientas básicas para diseñar código mantenible. ¡Úsalas! :)