Both sides previous revisionPrevious revisionNext revision | Previous revision |
wiki:psp_1 [2024/08/24 23:13] – admin | wiki:psp_1 [2024/08/28 23:52] (current) – admin |
---|
</code> | </code> |
| |
En el ejemplo también existe un acoplamiento entre ''B'' y ''File''. Sin embargo, este es un acoplamiento aceptable, porque ''B'' realmente necesita persistir sus datos. Entonces, para conseguir esto, nada mejor que usar una clase de la biblioteca de entrada y salida del lenguaje de programación. | En el ejemplo también existe un acoplamiento entre ''B'' y ''File''. Sin embargo, este es un acoplamiento aceptable, porque ''B'' realmente necesita persistir sus datos. Entonces, para conseguir esto, nada mejor que usar una clase de la biblioteca de entrada y salida del lenguaje de programación. Una solución mejor para el acomplamiento entre las clases ''A'' y ''B'' de este ejemplo se muestra a continuación: |
| |
| <code java> |
| class A { |
| |
| private void f(B b) { |
| int total; |
| total = b.getTotal(); |
| ... |
| } |
| </code> |
| |
| <code java> |
| class B { |
| |
| int total; |
| |
| public int getTotal() { |
| return total; |
| } |
| |
| private void g() { |
| // calcula valor total |
| File arch = File.open("arch1"); |
| arch.writeInt(total); |
| ... |
| } |
| } |
| </code> |
| |
| En esta nueva versión, la dependencia entre ''A'' y ''B'' se hace explícita. Ahora, ''B'' tiene un método público que devuelve el valor total. Y la clase ''A'' tiene una dependencia de la clase ''B'' a través de un parámetro del método ''f''. Este parámetro se usa para solicitar explícitamente el valor total llamando al método ''getTotal()''. Como este método fue declarado público en ''B'', se espera que el desarrollador de esta clase se esfuerce por no alterar su firma. Por lo tanto, en esta nueva versión, decimos que, aunque existe una dependencia de ''A'' hacia ''B'', el acoplamiento creado por ella es aceptable. Es decir, no es un acoplamiento que genere preocupaciones. |
| |
| Aún en relación con el ejemplo anterior, es interesante mencionar que, en la primera versión, el código de ''A'' no declara ninguna variable o atributo del tipo ''B''. Y, aun así, tenemos un acoplamiento no aceptable entre las clases. En la segunda versión, ocurre lo contrario, ya que el método ''A.f()'' declara un parámetro del tipo ''B''. Aun así, el acoplamiento entre las clases es de mejor calidad, ya que es más fácil estudiar y mantener el código de ''A'' sin conocer los detalles de ''B''. |
| |
| Algunos autores utilizan también los términos acoplamiento estructural y acoplamiento evolutivo (o lógico) con el siguiente significado: |
| |
| * **Acoplamiento estructural** entre ''A'' y ''B'' ocurre cuando una clase ''A'' tiene una referencia explícita en su código a una clase ''B''. Por ejemplo, el acoplamiento entre ''Estacionamiento'' y ''Hashtable'' es estructural. |
| |
| * **Acoplamiento evolutivo (o lógico)** entre ''A'' y ''B'' ocurre cuando los cambios en la clase ''B'' tienden a propagarse a la clase ''A''. En el ejemplo mencionado, en el que la clase ''A'' depende de un entero almacenado en un archivo interno de ''B'', no existe acoplamiento estructural entre ''A'' y ''B'', ya que ''A'' no declara ninguna variable del tipo ''B'', pero existe acoplamiento evolutivo. Por ejemplo, los cambios en el formato del archivo creado por ''B'' tendrán un impacto en ''A''. |
| |
| El acoplamiento estructural puede ser aceptable o no aceptable, dependiendo de la estabilidad de la interfaz de la clase de destino. El acoplamiento evolutivo, especialmente cuando cualquier cambio en ''B'' se propaga a la clase de origen ''A'', representa un acoplamiento no aceptable. |
| |
| Kent Beck, cuando trabajaba en Facebook, creó un glosario de términos relacionados con el diseño de software. En este glosario, acoplamiento se define de la siguiente forma [[https://engineering.fb.com/2011/09/27/uncategorized/software-design-glossary/|(enlace)]]: |
| |
| |
| <blockquote> |
| <color black>Dos elementos están acoplados cuando los cambios en un elemento requieren cambios en otro elemento. El acoplamiento puede dar lugar a una relación muy sutil entre clases, como observamos frecuentemente en Facebook. Ciertos eventos que interrumpen el funcionamiento de una parte del sistema suelen ser causados por pequeños fragmentos de acoplamiento que no se esperaban; por ejemplo, cambios en la configuración del sistema ''A'' causan un tiempo de espera en el sistema ''B'', lo que provoca una sobrecarga en el sistema ''C''. </color> |
| </blockquote> |
| |
| La definición de acoplamiento propuesta por Beck — cuando los cambios en un elemento requieren cambios en otro elemento — corresponde a la definición de acoplamiento evolutivo. Es decir, parece que Beck no se preocupa por el acoplamiento aceptable (es decir, estructural y estable) entre dos clases; ya que, de hecho, no debería ser motivo de preocupación. |
| |
| El comentario también deja claro que el acoplamiento puede ser indirecto. Es decir, los cambios en A pueden propagarse a ''B'', y luego alcanzar a ''C''. En ese caso, ''C'' está acoplado a ''A'', pero de forma indirecta. |
| |
| **Mundo Real:** Un ejemplo de problema real causado por acoplamiento indirecto se conoció como el episodio de left-pad. En 2016, una disputa de derechos de autor motivó a un desarrollador a eliminar una de sus bibliotecas del repositorio npm, muy utilizado para almacenar y distribuir bibliotecas de node.js/JavaScript. La biblioteca eliminada — llamada left-pad — tenía una única función de JavaScript, llamada leftPad, con solo 11 líneas de código. Esta función rellenaba una cadena de caracteres con espacios a la izquierda. Por ejemplo, ''leftPad('foo', 5)'' devolvería ''' foo''', es decir, '''foo''' con dos espacios a la izquierda. |
| |
| Miles de sistemas web dependían de esta función trivial, pero la dependencia ocurría de manera indirecta. Los sistemas usaban npm para descargar dinámicamente el código JavaScript de una biblioteca B1, que a su vez dependía de una biblioteca B2 cuyo código también estaba en npm, y así sucesivamente, hasta alcanzar una biblioteca Bn que dependía de left-pad. Como resultado, todos los sistemas que dependían de left-pad — de forma directa o indirecta — quedaron fuera de servicio durante algunas horas, hasta que la biblioteca se volvió a insertar en npm. En resumen, los sistemas se vieron afectados por un problema en una biblioteca trivial, y no tenían la menor idea de que estaban acoplados a ella. |
| |
| |
| ====== Principios de Diseño SOLID y Otros ====== |
| |
| Los principios de diseño son recomendaciones más concretas que los desarrolladores de software deben seguir para cumplir con las propiedades de diseño. Así, las propiedades de diseño pueden verse como recomendaciones aún genéricas (o tácticas), mientras que los principios ahora están en un nivel operativo. |
| |
| Estudiaremos los siete principios de diseño enumerados en la siguiente tabla. La tabla también muestra las propiedades de diseño que se contemplan al seguir cada uno de estos principios. |
| |
| ^ Principios de Diseño ^ Propiedades de Diseño ^ |
| | [[wiki:pru|Responsabilidad Única]] | Cohesión | |
| | [[wiki:psi|Segregación de Interfaces]] | Cohesión | |
| | [[wiki:pid|Inversión de Dependencias]] | Acoplamiento | |
| | [[wiki:ppch|Preferir Composición sobre Herencia]] | Acoplamiento | |
| | [[wiki:pd|Demeter]] | Ocultamiento de la Información | |
| | [[wiki:pac|Abierto/Cerrado]] | Extensibilidad | |
| | [[wiki:psl|Sustitución de Liskov]] | Extensibilidad | |
| |
| Cinco de los principios que vamos a estudiar son conocidos como los Principios SOLID, un acrónimo acuñado por Robert Martin y Michael Feathers [[https://dl.acm.org/doi/book/10.5555/3175742|(enlace)]]. Este acrónimo deriva de la letra inicial de cada principio en inglés: |
| |
| * **S**ingle Responsibility Principle (Principio de Responsabilidad Única) |
| * **O**pen/Closed Principle (Principio Abierto/Cerrado) |
| * **L**iskov Substitution Principle (Principio de Sustitución de Liskov) |
| * **I**nterface Segregation Principle (Principio de Segregación de Interfaces) |
| * **D**ependency Inversion Principle (Principio de Inversión de Dependencias) |
| |
| Los principios de diseño que vamos a estudiar tienen un punto en común: no solo buscan resolver un problema, sino también asegurar que la solución encontrada pueda mantenerse y evolucionar con éxito en el futuro. Los mayores problemas con los proyectos de software suelen ocurrir después de la implementación, cuando el sistema necesita ser mantenido. Normalmente, existe una tendencia a que este mantenimiento se vuelva gradualmente más lento, costoso y arriesgado. Por lo tanto, los principios de diseño que estudiaremos intentan reducir o posponer esta continua degradación de la calidad interna de los sistemas de software. En resumen, el objetivo no es solo entregar un diseño capaz de resolver un problema, sino también facilitar los mantenimientos futuros. Recuerda que la principal regla sobre los requisitos de software es que cambian con frecuencia. Lo mismo ocurre con las tecnologías de implementación, como bibliotecas y frameworks. |