En ingeniería de software, ¿cuál es la diferencia entre alto acoplamiento y baja cohesión?

La cohesión se refiere al grado en que los elementos de un módulo / clase pertenecen juntos.

Couplin g se refiere al grado en que los diferentes módulos / clases dependen unos de otros.

Como ambos son dos conceptos diferentes y se refieren a dos partes diferentes del proyecto / código, pero ambos términos se usan colectivamente como a continuación.

Sistemas de alto acoplamiento, baja cohesión : sistema en el que varios módulos dependen en gran medida de los otros módulos (alto acoplamiento) y los elementos de cada módulo están relacionados entre sí en muy baja extensión (baja cohesión).

Sistemas de bajo acoplamiento y alta cohesión : sistemas en los que los módulos dependen unos de otros en un grado muy bajo (acoplamiento bajo) y todos los elementos relacionados en el código están unidos tanto como sea posible.

El acoplamiento bajo y la alta cohesión es un fenómeno recomendado: todo el código relacionado debe estar cerca uno del otro y debemos esforzarnos por lograr una alta cohesión y unir todo el código relacionado en la medida de lo posible y todos los módulos deben ser independientes en la medida de lo posible, por lo que deben esforzarse para bajo acoplamiento.

El acoplamiento es malo. Cuando las cosas no van juntas y están juntas o hay una gran dependencia entre ellas, tienes acoplamiento.

La cohesión es cuando las cosas van juntas. Entonces, la alta cohesión hace que un sistema sea coherente. La misma raíz de la palabra.

Bajo acoplamiento y alta cohesión es alguna propiedad deseada de un sistema.

Los patrones de diseño son una forma de reducir el acoplamiento.

Cohesión es simplemente poner las cosas juntas cuando van juntas.

Dicho esto, necesita dependencias, sería imposible programar si un código no puede llamar a otro.

Entonces, la verdadera pregunta es ¿dónde pongo las dependencias?

Hay varias tácticas utilizadas para eliminar dependencias, o al menos para hacerlas explícitas:

  1. Eliminar código duplicado. Si ha esuplicado el código, entonces tiene una dependencia pero no es explícito y el código es realmente difícil de cambiar.
  2. Escriba pequeños métodos de no más de 10 líneas. De esa manera, su código puede leerse, y puede darse cuenta de todas las repeticiones que tiene en el código.
  3. Hasta ahora solo está detectando dependencias, ahora necesita hacer un código particular para depender del código abstracto, no al revés. Debes esforzarte por escribir código abstracto y hacerlo funcionar para que siempre funcione, solo escribes módulos concretos que enchufas y listo.

El alto acoplamiento son detalles que no deberían ser conocidos para hacer el trabajo.

Usuarios de clase {
constructor () {
this.mySql = new MySqlConnection (“mysql: //192.168.0.1”);
}
crear usuario) {
this.mySql.exec (“insertar en usuarios (…)”);
}
}

Este es un ejemplo de un servicio que trata sobre usuarios y cómo salvar usuarios.
Pero sabe sobre MySQL, sobre qué dirección IP y a qué protocolo de comunicación se conecta la base de datos MySQL, el nombre de usuario / contraseña, sobre las consultas particulares que se necesitarían en qué tablas, en qué formatos …

Si tuviera que cambiar de MySQL a PostgreSQL, o a MongoDB, o Redis, o RethinkDB, tendría que reescribir toda la clase “Usuarios” desde cero.
Y si tienes docenas de estas clases, entonces tendrías que reescribir docenas de estas clases …

Un sistema débilmente acoplado es aquel que depende de la Inversión de dependencia y el polimorfismo / tipificación de pato, para lograr sus objetivos.

Usuarios de clase {
constructor (almacenamiento) {
this.storage = almacenamiento;
}
crear usuario) {
this.storage.insert (“Usuario”, usuario);
}
}

Aquí, me entregan un objeto de almacenamiento que espero que haga lo correcto.
En sistemas fuertemente tipados, dependería de una interfaz adecuada.

Habría un servicio que sabría cómo insertar valores (de forma segura) en tablas / entradas / archivos / etc.

Si quisiera usar MySQL o PostgreSQL o un archivo en el disco duro, o un servicio web, o el almacenamiento local del navegador, solo necesito reemplazar el tipo de ‘almacenamiento’ que le doy a la clase, y asegurarme de que el almacenamiento admite la interfaz que mi clase espera usar.

Ahora está vagamente acoplado. Sabe cómo hacer usuarios, y sabe cómo almacenarlos / buscarlos, y eso es todo.

La cohesión es diferente (aunque a menudo aparecerá junto a los problemas de acoplamiento como un efecto secundario del acoplamiento apretado).

“Cohesión” se trata de cuánto sentido tiene que las cosas se agrupen.

Ayer tuve esta charla sobre si las interfaces Java son “has-a” o “is-a”, por lo que, en una línea similar, aquí hay una versión muy ligera del ejemplo que solía mostrar que pueden ser:

interfaz MediaPlayer {
jugar ()
pausa ()
detener ()
buscar ()

carga ()
lanzamiento ()

añadir artículo ()
remover el artículo ()
moveItemUp ()
moveItemDown ()
seleccione un artículo ()
deselectItem ()
updateItems ()

updatePlaybackTimer ()
clearPlaybackTimer ()
updateScrubBar ()
// …
}

Esta es una interfaz de MediaPlayer, que un AudioPlayer podría implementar.

La clase AudioPlayer implementa MediaPlayer {
// …
}

Sin embargo, si observas, las partes y piezas del sistema realmente no van juntas.
No están realmente * sobre * hacer lo mismo.

Esto, para cierta marca de programador, sería una violación del Principio de Responsabilidad Única.

Diría que esta interfaz está desarticulada.

Es fácil imaginar que un BackgroundSongPlayer no necesite un temporizador de reproducción, o un administrador para una lista de pistas, o una barra de progreso, o botones de interfaz de usuario, etc.

De hecho, si miras un poco más de cerca, puedes sacar un MediaPlayer, un MediaLoader, un TracklistManager, un MediaController, etc., de esa interfaz monolítica.

Cada interfaz se basa en la resolución de problemas específicos, y cuando todos los bits de una interfaz (o todos los miembros públicos de un objeto, o lo que sea) tienen sentido para estar juntos, y es probable que todos sean utilizados por los consumidores (a través de uso previsto), entonces es una interfaz altamente cohesiva.

Entonces, el problema de cohesión versus acoplamiento aparece cuando volvemos a nuestro Usuario:

Usuario de clase {
constructor () {
this.mySql = new MySQLConnection ();
this.view = new UserPageView ();
}
connectToDatabase () {
this.mySql.connect (“…”);
}
está conectado () {
devuelve this.mySql.isConnected ();
}
showSuccessMessage (usuario) {
this.view.message = “Creado correctamente” + user.firstName;
}
updatePage () {
this.view.update ();
}

crear usuario) { }
find (userId) {}
actualización (usuario) {}
borrar usuario) { }
}

Aquí, debido a que la vista y la base de datos están estrechamente vinculadas al Usuario, nos encontramos con que tenemos que implementar todo tipo de métodos de ajuste (para que podamos pasar a un Usuario), para poder llamar a los métodos apropiados.

Ahora, tenemos un código de baja cohesión estrechamente acoplado (qué tiene que ver “updatePage” con “connectToDatabase” aparte del hecho de que el Usuario necesita hacer ambas cosas).

usuario de la estructura {
carné de identidad,
nombre primero Segundo },
años
}

interfaz CRUDPersisted {
crear (recordData)
encontrar (recordID)
actualizar (recordData)
eliminar (recordData)
}

clase UserService implementa CRUDPersisted {
constructor (db) {
this.db = db;
}
crear (userData) {
this.db.insert (“usuarios”, userData);
}
}

usuario = Usuario ({
id: 123
nombre: {primer: “Bob”, último: “McKenzie”},
edad: 32
});

UserService.create (usuario);
UserView.update (usuario);

Esa última parte es un pseudocódigo terrible que parece un montón de idiomas mezclados … … pero el punto no ha cambiado.

Al usar estructuras cohesivas apropiadas (y un acoplamiento flojo apropiado), evitamos muchos de los dolores de cabeza que podríamos tener en el otro mundo, y se hace más fácil componer nuestro sistema de diferentes maneras, si queremos pasar de HTML a JSON, o si queremos pasar de SQL a no-SQL, o distribuimos este sistema a través de microservicios que viven en clústeres de AWS.