This shows you the differences between two versions of the page.
Both sides previous revisionPrevious revisionNext revision | Previous revision | ||
wiki:psp_1 [2024/08/22 16:13] – [Ocultamiento de la Información] admin | wiki:psp_1 [2024/08/28 23:52] (current) – admin | ||
---|---|---|---|
Line 28: | Line 28: | ||
< | < | ||
- | <color black> | + | <color black> |
+ | < | ||
</ | </ | ||
Line 71: | Line 72: | ||
</ | </ | ||
+ | Esta clase tiene un problema de exposición excesiva de información o, en otras palabras, no oculta estructuras que pueden cambiar en el futuro. Específicamente, | ||
+ | Supongamos que el sistema de estacionamiento fuera manual, con los nombres de los vehículos anotados en una hoja de papel. Haciendo una comparación, | ||
+ | |||
+ | La siguiente versión de la clase es mejor, ya que encapsula la estructura de datos responsable de almacenar los vehículos. Ahora existe el método estaciona para estacionar un vehículo. Con esto, los desarrolladores de la clase tienen la libertad de cambiar la estructura de datos sin causar impacto en sus clientes. La única restricción es que la firma de estaciona debe ser preservada. | ||
+ | |||
+ | |||
+ | <code java> | ||
+ | import java.util.Hashtable; | ||
+ | |||
+ | public class Estacionamiento { | ||
+ | |||
+ | private Hashtable< | ||
+ | |||
+ | public Estacionamiento() { | ||
+ | vehiculos = new Hashtable< | ||
+ | } | ||
+ | |||
+ | public void estaciona(String patente, String vehiculo) { | ||
+ | vehiculos.put(patente, | ||
+ | } | ||
+ | |||
+ | public static void main(String[] args) { | ||
+ | Estacionamiento e = new Estacionamiento(); | ||
+ | e.estaciona(" | ||
+ | e.estaciona(" | ||
+ | e.estaciona(" | ||
+ | } | ||
+ | } | ||
+ | </ | ||
+ | |||
+ | En resumen, esta nueva versión oculta una estructura de datos - sujeta a alteraciones durante la evolución del sistem - y proporciona una interfaz estable para los clientes da la clase - representada por el método // | ||
+ | |||
+ | ===== Cohesión ===== | ||
+ | |||
+ | La implementación de cualquier clase debe ser cohesiva, es decir, cada clase debe implementar una única funcionalidad o servicio. Específicamente, | ||
+ | |||
+ | La cohesión tiene las siguientes ventajas: | ||
+ | |||
+ | * Facilita la implementación de una clase, así como su comprensión y mantenimiento. | ||
+ | * Facilita la asignación de un único responsable para mantener una clase. | ||
+ | * Facilita la reutilización y prueba de una clase, ya que es más sencillo reutilizar y probar una clase cohesiva que una clase con múltiples responsabilidades. | ||
+ | |||
+ | La separación de intereses (separation of concerns) es otra propiedad deseable en los proyectos de software, la cual es similar al concepto de cohesión. Defiende que una clase debe implementar solo un interés (concern). En este contexto, el término interés se refiere a cualquier funcionalidad, | ||
+ | |||
+ | ==== Ejemplos ==== | ||
+ | |||
+ | 1. La discusión anterior nos dice que las clases deben ser cohesivas, sin embargo, el concepto se adapta también a métodos y funciones. Por ejemplo, suponga una función como la siguiente: | ||
+ | |||
+ | |||
+ | <code java> | ||
+ | |||
+ | float sin_or_cos(double x, int op) { | ||
+ | if (op == 1) | ||
+ | " | ||
+ | else | ||
+ | " | ||
+ | } | ||
+ | |||
+ | </ | ||
+ | |||
+ | Esta función - que consiste en un ejemplo extremo y, que en la práctica seria poco común - presenta un problema serio de cohesión porque realiza dos cosas: calcula el seno o coseno de su argumento. Lo recomendable seria crear funciones separadas para cada una de estas tareas. | ||
+ | |||
+ | 2. Suponga ahora la siguiente clase: | ||
+ | |||
+ | <code java> | ||
+ | |||
+ | class Stack< | ||
+ | boolean empty() { ... } | ||
+ | T pop() { ... } | ||
+ | push (T) { ... } | ||
+ | int size() { ... } | ||
+ | } | ||
+ | |||
+ | </ | ||
+ | |||
+ | Se trata de una clase cohesiva, porque todos lo métodos implementan operaciones importantes en una estructura de datos del tipo Pila. | ||
+ | |||
+ | ===== Acoplamiento | ||
+ | |||
+ | El acoplamiento es la fuerza de la conexión entre dos clases. Aunque pueda parecer simple, el concepto tiene algunas matices, las cuales derivan de la existencia de dos tipos de acoplamiento entre clases: acoplamiento aceptable y acoplamiento no aceptable. | ||
+ | |||
+ | Decimos que existe un acoplamiento aceptable de una clase A a una clase B cuando: | ||
+ | |||
+ | La clase A usa solo métodos públicos de la clase B. | ||
+ | |||
+ | La interfaz proporcionada por B es estable desde el punto de vista sintáctico y semántico. Es decir, las firmas de los métodos públicos de B no cambian con frecuencia; y lo mismo ocurre con el comportamiento externo de dichos métodos. Por eso, son raros los cambios en B que tendrán un impacto en la clase A. | ||
+ | |||
+ | Por otro lado, existe un acoplamiento no aceptable de una clase A a una clase B cuando los cambios en B pueden impactar fácilmente a A. Esto ocurre principalmente en las siguientes situaciones: | ||
+ | |||
+ | * Cuando la clase A realiza un acceso directo a un archivo o base de datos de la clase B. | ||
+ | |||
+ | * Cuando las clases A y B comparten una variable o estructura de datos global. Por ejemplo, la clase B altera el valor de una variable global que la clase A usa en su código. | ||
+ | |||
+ | * Cuando la interfaz de la clase B no es estable. Por ejemplo, los métodos públicos de B se renombran con frecuencia. | ||
+ | |||
+ | En esencia, lo que caracteriza el acoplamiento no aceptable es el hecho de que la dependencia entre las clases no está mediada por una interfaz estable. Por ejemplo, cuando una clase altera el valor de una variable global, no es consciente del impacto de ese cambio en otras partes del sistema. Por otro lado, cuando una clase altera su interfaz, es consciente de que eso tendrá un impacto en los clientes, pues la función de una interfaz es precisamente anunciar los servicios que una clase ofrece al resto del sistema. | ||
+ | |||
+ | En resumen: el acoplamiento puede ser de gran utilidad, especialmente cuando ocurre con la interfaz de una clase estable que presta un servicio relevante para la clase de origen. Sin embargo, el acoplamiento no aceptable debe evitarse, ya que es un acoplamiento no mediado por interfaces. Los cambios en la clase de destino del acoplamiento pueden propagarse fácilmente a la clase de origen. | ||
+ | |||
+ | Frecuentemente, | ||
+ | |||
+ | < | ||
+ | <color black> | ||
+ | </ | ||
+ | |||
+ | De hecho, si una clase depende de muchas otras clases, por ejemplo, de decenas de clases, puede estar asumiendo demasiadas responsabilidades en forma de funcionalidades no cohesivas. Recuerda que una clase debe tener una única responsabilidad (o un único motivo para ser modificada). Por otro lado, debemos tener cuidado con el significado del verbo minimizar. El objetivo no debe ser eliminar completamente el acoplamiento de una clase con otras clases, ya que es natural que una clase necesite de otras clases, especialmente de aquellas que implementan servicios básicos, como estructuras de datos, entrada/ | ||
+ | |||
+ | - Recuerde la clase '' | ||
+ | * '' | ||
+ | * La interfaz de '' | ||
+ | - Suponga el siguiente trecho de código, en el cual existen un archivo compartido por dos clases, '' | ||
+ | |||
+ | <code java> | ||
+ | class A { | ||
+ | private void f() { | ||
+ | int total; ... | ||
+ | File arch = File.open(" | ||
+ | total = arch.readInt(); | ||
+ | ... | ||
+ | } | ||
+ | } | ||
+ | </ | ||
+ | |||
+ | <code java> | ||
+ | class B { | ||
+ | private void g() { | ||
+ | int total; | ||
+ | // calcula valor total | ||
+ | File arch = File.open(" | ||
+ | arch.writeInt(total); | ||
+ | ... | ||
+ | arch.close(); | ||
+ | } | ||
+ | </ | ||
+ | |||
+ | En el ejemplo también existe un acoplamiento entre '' | ||
+ | |||
+ | <code java> | ||
+ | class A { | ||
+ | |||
+ | private void f(B b) { | ||
+ | int total; | ||
+ | total = b.getTotal(); | ||
+ | ... | ||
+ | } | ||
+ | </ | ||
+ | |||
+ | <code java> | ||
+ | class B { | ||
+ | |||
+ | int total; | ||
+ | |||
+ | public int getTotal() { | ||
+ | return total; | ||
+ | } | ||
+ | |||
+ | private void g() { | ||
+ | // calcula valor total | ||
+ | File arch = File.open(" | ||
+ | arch.writeInt(total); | ||
+ | ... | ||
+ | } | ||
+ | } | ||
+ | </ | ||
+ | |||
+ | En esta nueva versión, la dependencia entre '' | ||
+ | |||
+ | Aún en relación con el ejemplo anterior, es interesante mencionar que, en la primera versión, el código de '' | ||
+ | |||
+ | Algunos autores utilizan también los términos acoplamiento estructural y acoplamiento evolutivo (o lógico) con el siguiente significado: | ||
+ | |||
+ | * **Acoplamiento estructural** entre '' | ||
+ | |||
+ | * **Acoplamiento evolutivo (o lógico)** entre '' | ||
+ | |||
+ | 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 '' | ||
+ | |||
+ | 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:// | ||
+ | |||
+ | |||
+ | < | ||
+ | <color black> | ||
+ | </ | ||
+ | |||
+ | 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 '' | ||
+ | |||
+ | **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/ | ||
+ | |||
+ | 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, | ||
+ | |||
+ | |||
+ | ====== 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: | ||
+ | | [[wiki: | ||
+ | | [[wiki: | ||
+ | | [[wiki: | ||
+ | | [[wiki: | ||
+ | | [[wiki: | ||
+ | | [[wiki: | ||
+ | |||
+ | 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:// | ||
+ | |||
+ | * **S**ingle Responsibility Principle (Principio de Responsabilidad Única) | ||
+ | * **O**pen/ | ||
+ | * **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, |