A los que sobre todo han ido a la Universidad a estudiar asignaturas de programación con orientación a objetos les sonarán los conceptos de acoplamiento, cohesión y encapsulación. Son conceptos muy fáciles de entender, pero que normalmente muchos desarrolladores no aplican a sus proyectos. Muy probablemente se deba a que estos desarrolladores, a pesar de conocer estos conceptos, no entienden los beneficios de usar diseños con bajos niveles de acoplamiento, alta cohesión y una buena encapsulación de los componentes.
En esta entrada quiero mostrar las ventajas de usar correctamente estos principios y como nos podría ayudar en os desarrollos que estemos haciendo.
Acoplamiento
En desarrollo software, el acoplamiento se puede definir como el grado en el que dos ó más componentes o clases dependen unas de otras.
Un buen diseño o una buena arquitectura tratará de mantener un nivel de acoplamiento bajo. Me explico con un ejemplo: supongamos que tenemos un software con la siguiente arquitectura de componentes donde se muestran las dependencias que hay entre estos: Ahora tenemos otro desarrollo distinto, y vemos que una de las funcionalidades ya la realiza el Módulo C del proyecto de la imagen. Perfecto, cogemos ese módulo y lo usamos en nuestro nuevo desarrollo. En seguida nos damos cuenta que es más fácil decirlo que hacerlo. Tiene demasiadas dependencias con otros componentes que a su vez también tienen otras dependencias. Por lo que usar el componente C nos va a suponer también traernos muchos de los otros componentes de los que depende. Es decir, el componente C tiene un grado de acoplamiento muy alto con el resto de componentes.
“Si un componente tiene un alto grado de acoplamiento con otros componentes, entonces para usar ese componente vas a tener que usar también los otros de los que dependen aunque no los necesites.”
Se puede reducir el nivel de acoplamiento de un componente de muchas maneras. La más usual es la de usar interfaces o herencia de clases entre los componentes que desacoplen la dependencia entre estos.
Por el lado contrario, no existen los componentes con nivel de acoplamiento cero. No tienen sentido. Un módulo que no depende de nada y del que nadie depende, ¿qué uso puede tener?
En definitiva, el acoplamiento no es algo estrictamente malo. Los componentes que forman parte de un sistema deben de poder interactuar unos con otros para que este pueda realizar alguna función. Por tanto, el objetivo es alcanzar un equilibrio en el grado de acoplamiento de los componentes de manera que sea funcional (que haga lo que tiene que hacer), entendible y mantenible por otros desarrolladores.
Cohesión
El grado de cohesión de un componente es la relación funcional que existe entre las funciones de ese componente para realizar una tarea. Es decir, un módulo coherente es capaz de realizar una tarea sin necesidad de interactuar con otros componentes.
Haciendo una extrapolación para sistemas complejos, la cohesión define cómo los distintos componentes que forman parte del sistema se relacionan e interaccionan para crear un sistema que da un valor añadido mayor que el que pueden dar la suma de sus partes.
“El todo es mayor que la suma de las partes.”
Este es un concepto mucho más abstracto y quizás más difícil de conseguir debido a que no hay modelos ni recetas mágicas que nos digan como conseguir desarrollar software con un alto grado de cohesión. Sin embargo es un concepto que es bastante usado de la vida cotidiana. Muchas veces hemos oído aplicarlo al deporte: Equipos con alto valor de cohesión consiguen mejores resultados en las competiciones en las que participan que otros menos cohesionados aunque sus jugadores sean mucho mejores. También las empresas buscan esa cohesión: tener equipos de trabajoque trabajen juntos para conseguir productos a tiempo con la mejor calidad posible.
Supongamos la arquitectura del sistema de la imagen siguiente.
Si se analizan por separado cada uno de los componentes del sistema realmente no se tiene gran cosa. Por separado cada componente realiza una serie de tareas muy limitadas con poco valor por si mismo. Sin embargo, cuando se juntan los componentes y se hace que interaccionen coherentemente entre sí, el valor aportado es muchísimo mayor.
Para conseguir hacer software con un nivel de cohesión elevado debemos tratar de huir de las mega-clases y mega-componentes que hacen de todo. Es posible que internamente estén bien cohesionados, pero a nivel de sistema no será así. Por ejemplo, que un componente sea capaz de leer los datos de la base de datos, procesarlos y mostrarlos en algún tipo de interfaz de usuario.
Hay muchos patrones que están enfocados a lograr buenos niveles de cohesión. Por ejemplo el multi-capa (como el del diagrama anterior), MVC, MVVM, etc.
Encapsulación
El grado de encapsulación de un componente viene definido por la forma en el que el componente oculta la información superflua y la lógica de lo que hace al resto de componentes del sistema.
Y es que muchos desarrolladores entiende por encapsulación sólo la ocultación de información, dejando de lado la lógica que los componentes implementan.
Supongamos un diseño en el que la clase X está bien encapsulado. Entre otras ventajas, el hecho de ocultar la lógica hacia el exterior hace que podamos cambiar la implementación interna sin que afecte al resto de clases del diseño. Por otro lado, si hacemos que la funcionalidad de la clase X se base en la implementación de interfaces, los componentes que necesiten usar X se referirán a ella por sus interfaces y no por la clase en sí.
Por ejemplo, supongamos este par de interfaces sencillas definidas en C#:
public interface IAnimal
{
string Especie { get; }
void Comer();
void Andar();
void Atacar();
}
public interface IPersona
{
string Nombre { get; set; }
DateTime FechaNacimiento { get; set; }
void Trabajar();
void Hablar();
}
public class HombreLobo : IAnimal, IPersona
{
#region IAnimal Members
public string Especie { get { return "Lobo"; } }
public void Comer()
{
// ...
}
public void Andar()
{
// ...
}
public void Atacar()
{
// ...
}
#endregion
#region IPersona Members
public string Nombre {get; set;}
public DateTime FechaNacimiento {get; set;}
public void Trabajar()
{
// ...
}
public void Hablar()
{
// ...
}
#endregion
}
Los componentes que quieran usar esta clase lo pueden hacer basándose en las interfaces que la clase implementa:
public class MiClase
{
public void HacerAlgo()
{
IAnimal lobo = new HombreLobo();
lobo.Atacar();
lobo.Comer();
}
}
Una correcta encapsulación de las clases o componentes nos ayuda a reducir la duplicación de datos y de procesos en nuestros desarrollos y nos va a ayudar en situaciones donde necesitemos usar alguna funcionalidad proporcionada por alguna de nuestras clases en más de un sitio.
Para terminar
Cada uno de los principios que hemos visto están conectados entre sí: El desarrollo de componentes fuertemente encapsulados facilita que consigamos disminuir el nivel de acoplamiento de estos componentes. Y por consecuencia, tener encapsulación y bajo acoplamiento nos ayudará a asegurarnos el tener un sistema altamente cohesionado.