Acoplamiento. Mejores prácticas

11

Siguiendo este hilo comencé

El patrón Singleton

Me hizo pensar en qué tan acopladas están mis clases y cuál es la mejor manera de lograr un acoplamiento flexible. Tenga en cuenta que soy un nuevo programador (4 meses en mi primer trabajo) y esta es realmente la primera consideración que le he dado a esto, y estoy muy interesado en comprender el concepto.

Entonces, ¿qué constituye exactamente el acoplamiento flojo frente al acoplamiento pesado? En mi proyecto actual (y primer proyecto), estoy trabajando en un proyecto ac # winforms, donde la sección GUI crea objetos y se suscribe a sus eventos, cuando se activan, la GUI crea otro objeto (en este ejemplo, una vista de cuadrícula de datos (una clase que he creado que se ajusta a una vista de cuadrícula de datos estándar y agrega funcionalidad adicional) y la adjunta a la GUI. ¿Es este mal acoplamiento o es bueno?

Realmente no quiero tener malos hábitos y comenzar a codificar mal, por lo tanto, agradecería sus respuestas.

Darren Young
fuente

Respuestas:

11

Para hacer que su código se acople libremente aquí hay algunas cosas simples para recordar:

Parte 1:

Técnicamente conocido como "Separación de preocupaciones". Cada clase tiene un rol específico, debe manejar la lógica de negocios o la lógica de la aplicación. Trate de mantenerse alejado de la clase que combina ambas responsabilidades. es decir, una clase que gestiona datos (a largo plazo) es la lógica de la aplicación, mientras que una clase que utiliza datos es la lógica de negocios.

Personalmente me refiero a esto (en mi propio pequeño mundo) como create it or use it. Una clase debe crear un objeto o usar un objeto que nunca debe hacer ambas cosas.

Parte 2:

Cómo implementar la separación de preocupaciones.
Como punto de partida hay dos técnicas simples:

Nota: Los patrones de diseño no son absolutos.
Se supone que están personalizados para la situación, pero tienen un tema subyacente que es similar a todas las aplicaciones. Así que no mire los ejemplos a continuación y diga que debo seguir esto rígidamente; estos son solo ejemplos (y un poco artificiales).

Inyección de dependencia :

Aquí es donde pasa un objeto que usa una clase. El objeto que pasa basado en una interfaz para que su clase sepa qué hacer con él, pero no necesita saber la implementación real.

class Tokenizer
{
    public:
        Tokenizer(std::istream& s)
            : stream(s)
        {}
        std::string nextToken() { std::string token; stream >> token;return token;}
    private:
        std::istream& stream;
};

Aquí inyectamos la secuencia en un Tokenizer. El tokenizer no sabe de qué tipo es la secuencia, siempre que implemente la interfaz de std :: istream.

Patrón de localizador de servicio :

El patrón del localizador de servicios es una ligera variación en la inyección de dependencia. En lugar de dar un objeto que pueda usar, le pasa un objeto que sabe cómo ubicar (crear) el objeto que desea usar.

class Application
{
     public:
         Application(Persister& p)
             : persistor(p)
         {}

         void save()
         {
             std::auto_ptr<SaveDialog> saveDialog = persistor.getSaveDialog();
             saveDialog.DoSaveAction();
         }

         void load()
         {
             std::auto_ptr<LoadDialog> loadDialog = persistor.getLoadDialog();
             loadDialog.DoLoadAction();
         }
    private:
        Persister& persistor;
};

Aquí pasamos el objeto de aplicación un objeto persistente. Cuando realiza una acción de guardar / cargar, utiliza el persistor para crear un objeto que realmente sabe cómo realizar la acción. Nota: Una vez más, el persistor es una interfaz y puede proporcionar diferentes implementaciones según la situación.

Esto es útil cuando potentiallyse requiere un objeto único cada vez que crea una instancia de una acción.

Personalmente, creo que esto es particularmente útil para escribir pruebas unitarias.

Nota de patrones:

Los patrones de diseño son un tema enorme en sí mismo. De ninguna manera es una lista exclusiva de patrones que puede usar para ayudar con el acoplamiento suelto; Este es solo un punto de partida común.

Con experiencia, se dará cuenta de que ya está usando estos patrones, es solo que no usó sus nombres formales. Al estandarizar sus nombres (y hacer que todos los aprendan) descubrimos que es más fácil y rápido comunicar ideas.

Martin York
fuente
3
@ Darren Young: Gracias por aceptarlo. Pero tu pregunta tiene solo tres horas. Volvería en un día más o menos para verificar que otros no hayan proporcionado mejores respuestas.
Martin York
Gracias por la excelente respuesta. Con respecto a la separación de la preocupación ... Tengo clases que requieren algunos datos para su uso y luego hago algo con esos datos. Así es como en el pasado he diseñado clases. Por lo tanto, ¿sería mejor crear una clase que obtenga los datos y los adapte a la forma correcta y luego otra clase para usar los datos?
Darren Young
@Darren Young: Al igual que con toda la programación, la línea es gris y borrosa no está bien definida. Es difícil dar una respuesta exacta sin leer su código. Pero cuando hablo managing the datame refiero a las variables (no a los datos reales). Por lo tanto, cosas como los punteros deben gestionarse para que no se filtren. Pero los datos se pueden inyectar o la forma en que se recuperan los datos se puede abstraer (para que su clase se pueda reutilizar con diferentes métodos de recuperación de datos). Lo siento, no puedo ser más preciso.
Martin York
1
@Darren Young: Como señaló @StuperUser en su respuesta. No vaya por la borda (AKA minutae of loose coupling(me encanta esa palabra minutae)). El secreto de la programación es aprender cuándo usar las técnicas. El uso excesivo puede conducir a una maraña de código.
Martin York
@ Martin - gracias por el consejo. Creo que es donde estoy luchando en este momento ... Tiendo a preocuparme constantemente por la arquitectura de mi código y también tratando de aprender el lenguaje específico que estoy usando C #. Supongo que vendrá con experiencia y mi deseo de aprender estas cosas. Agradezco tus comentarios.
Darren Young
6

Soy un desarrollador de ASP.NET, así que no sé mucho sobre el acoplamiento de WinForms, pero sé un poco de las aplicaciones web N-Tier, suponiendo una arquitectura de aplicación de 3 niveles de UI, Dominio, Capa de acceso a datos (DAL).

El acoplamiento flojo se trata de abstracciones.

Como @MKO dice que si puede reemplazar un ensamblaje con otro (por ejemplo, un nuevo proyecto de interfaz de usuario que usa su proyecto de dominio, un nuevo DAL que se guarda en una hoja de cálculo en lugar de una base de datos), entonces hay un acoplamiento flojo. Si su dominio y DAL dependen de proyectos más adelante en la cadena, entonces el acoplamiento podría ser más flojo.

Un aspecto de algo que se acopla libremente es si puede reemplazar un objeto con otro que implemente la misma interfaz. No depende del objeto real, sino de la descripción abstracta de lo que hace (su interfaz).
El acoplamiento flojo, las interfaces y los Inyectores de dependencia (DI) y la Inversión de control (IoC) son útiles para el aspecto de aislamiento del diseño para la comprobabilidad.

Por ejemplo, un objeto en el proyecto de IU llama a un objeto Repository en el proyecto de dominio.
Puede crear un objeto falso que implemente la misma interfaz que el repositorio que usa el código bajo prueba, luego escriba un comportamiento especial para las pruebas ( resguardos para evitar que se llame al código de producción que guarda / elimina / obtiene y simula que actúan como resguardos y realizan un seguimiento del estado del objeto falso para fines de prueba).
Esto significa que el único código de producción que se llama ahora solo está en su objeto de interfaz de usuario, su prueba será solo contra ese método y cualquier falla de prueba aislará el defecto de ese método.

Además, en el menú Analizar en VS (dependiendo de la versión que tenga) hay herramientas para Calcular Métricas de Código para su proyecto, una de las cuales es el Acoplamiento de Clase, habrá más información sobre esto en la documentación de MSDN.

No te TOO empantanado en el minutae de acoplamiento débil sin embargo, si no hay ninguna posibilidad de que las cosas son para ser reutilizado (por ejemplo, un proyecto de dominio con más de una interfaz de usuario) y la vida útil del producto es pequeño, entonces el acoplamiento débil se vuelve menos de prioridad (aún se tendrá en cuenta), pero seguirá siendo responsabilidad de los arquitectos / líderes tecnológicos que revisarán su código.

StuperUser
fuente
3

El acoplamiento se refiere al grado de conocimiento directo que una clase tiene de otra . Esto no debe interpretarse como encapsulación versus no encapsulación. No es una referencia al conocimiento de una clase de los atributos o implementación de otra clase, sino más bien el conocimiento de esa otra clase en sí. El acoplamiento fuerte se produce cuando una clase dependiente contiene un puntero directamente a una clase concreta que proporciona el comportamiento requerido. La dependencia no puede ser sustituida, o su "firma" cambiada, sin requerir un cambio en la clase dependiente. El acoplamiento flexible se produce cuando la clase dependiente contiene un puntero solo a una interfaz, que luego puede implementarse mediante una o varias clases concretas.La dependencia de la clase dependiente es a un "contrato" especificado por la interfaz; Una lista definida de métodos y / o propiedades que las clases de implementación deben proporcionar. Cualquier clase que implemente la interfaz puede satisfacer la dependencia de una clase dependiente sin tener que cambiar la clase. Esto permite la extensibilidad en el diseño de software; se puede escribir una nueva clase que implemente una interfaz para reemplazar una dependencia actual en algunas o todas las situaciones, sin requerir un cambio en la clase dependiente; Las clases nuevas y antiguas se pueden intercambiar libremente. El acoplamiento fuerte no permite esto. Enlace de referencia.

Amir Rezaei
fuente
3

Eche un vistazo a los 5 principios SÓLIDOS . Al adherirse al SRP, el ISP y el DIP reducirán significativamente el acoplamiento, siendo el DIP el más poderoso. Es el principio fundamental debajo de la DI ya mencionada .

Además, vale la pena echarle un vistazo a GRASP . Es una mezcla extraña entre conceptos abstractos (al principio le resultará difícil de implementar) y patrones concretos (que en realidad podrían ser de ayuda), pero la belleza es probablemente la menor de sus preocupaciones en este momento.

Y por último, puede encontrar esta sección sobre IoC bastante útil, como punto de entrada a técnicas comunes.

De hecho, encontré una pregunta sobre stackoverflow , donde demuestro la aplicación de SOLID en un problema concreto. Podría ser una lectura interesante.

back2dos
fuente
1

De acuerdo con Wikipedia:

En computación y diseño de sistemas, un sistema débilmente acoplado es aquel en el que cada uno de sus componentes tiene, o hace uso, de poco o ningún conocimiento de las definiciones de otros componentes separados.

El problema con el acoplamiento apretado hace que sea difícil hacer cambios. (Muchos escritores parecen sugerir que esto causa problemas principalmente durante el mantenimiento, pero en mi experiencia también es relevante durante el desarrollo inicial). Lo que tiende a suceder en sistemas estrechamente acoplados es que un cambio en un módulo del sistema requiere cambios adicionales. en los módulos a los que está acoplado. La mayoría de las veces, esto requiere más cambios en otros módulos, etc.

Por el contrario, en un sistema débilmente acoplado, los cambios están relativamente aislados. Por lo tanto, son menos costosos y se pueden hacer con mayor confianza.

En su ejemplo particular, el manejo de eventos proporciona cierta separación entre la GUI y los datos subyacentes. Sin embargo, suena como si hubiera otras áreas de separación que podría explorar. Sin más detalles de su situación particular, es difícil ser específico. Sin embargo, puede comenzar con una arquitectura de 3 niveles que separe:

  • Almacenamiento de datos y lógica de recuperación
  • Lógica de negocios
  • Lógica requerida para ejecutar la IU

Algo a tener en cuenta es que para aplicaciones pequeñas y únicas con un único desarrollador, los beneficios de forzar el acoplamiento suelto en todos los niveles pueden no valer la pena. Por otro lado, las aplicaciones más grandes y complejas con múltiples desarrolladores son imprescindibles. Inicialmente, hay un costo incurrido al introducir las abstracciones y educar a los desarrolladores que no están familiarizados con el código con respecto a su arquitectura. Sin embargo, a la larga, el acoplamiento suelto ofrece ventajas que superan con creces los costos.

Si se toma en serio el diseño de sistemas acoplados libremente, lea sobre principios SÓLIDOS y patrones de diseño.

Sin embargo, lo importante es darse cuenta de que estos patrones y principios son solo eso: patrones y principios. Son no gobierna. Esto significa que deben aplicarse de forma pragmática e inteligente.

En lo que respecta a los patrones, es importante comprender que no existe una implementación "correcta" única de ninguno de los patrones. Tampoco son plantillas para cortar galletas para diseñar su propia implementación. Están allí para decirle qué forma podría tener una buena solución y para proporcionarle un lenguaje común para comunicar las decisiones de diseño con otros desarrolladores.

Todo lo mejor.

Kramii
fuente
1

Use inyección de dependencia, patrones de estrategia y eventos. En general: lea sobre los patrones de diseño, se trata de acoplamiento suelto y reducción de dependencias. Yo diría que los eventos se acoplan tan libremente como se obtendrían, mientras que la inyección de dependencia y los patrones de estrategia requieren algunas interfaces.

Un buen truco es colocar clases en diferentes bibliotecas / ensamblados, y hacer que dependan de la menor cantidad posible de otras bibliotecas, lo que lo obligará a refactorizar para usar menos dependencias

Homde
fuente
44
¿El condón es un término abstracto de TI o simplemente no estoy recibiendo el juego de palabras?
Darren Young
3
Es otro nombre para el contenedor de inyección de dependencia.
Mchl
2
También es una excelente manera de prevenir la propagación de virus. ¿Estamos hablando sobre la misma cosa?
sova
1
El acoplamiento pesado a menudo se realiza en el back
Homde
1
Establecer un titular no es abusar del formato
Homde
0

Permítanme proporcionar una vista alternativa. Solo pienso en esto en términos de que cada clase es una buena API. El orden en que se invocan los métodos es obvio. Lo que hacen es obvio. Redujo la cantidad de métodos al mínimo necesario. Por ejemplo,

init, abrir, cerrar

versus

setTheFoo, setBar, initX, getConnection, close

El primero es obvio y parece una buena API. El segundo puede causar errores si se llama en el orden incorrecto.

No me preocupo demasiado por tener que modificar y volver a compilar las personas que llaman. Mantengo MUCHO código, algunos nuevos y otros de 15 años. Por lo general, quiero errores del compilador cuando hago cambios. A veces, a propósito, romperé una API por ese motivo. Me da la oportunidad de considerar las ramificaciones de cada persona que llama. No soy un gran admirador de la inyección de dependencias porque quiero poder rastrear visualmente mi código sin cuadros negros y quiero que el compilador detecte tantos errores como sea posible.

Sean McEligot
fuente