Módulo 3 - Principios de Diseño
Módulo 3 - Principios de Diseño
Módulo 3 - Principios de Diseño
Contenido
Evaluación de la complejidad del diseño 2
Acomplamiento 3
Cohesión 3
Ocultación de información 9
Integridad Conceptual 12
Principios de generalización 14
Comprobación de modelo 22
Al finalizar este módulo, podrá:
Este módulo cubre las pautas generales para evaluar la estructura de las
soluciones de software. Estas pautas ayudan a garantizar que el software sea
flexible, reutilizable y mantenible. También cubrirá los comportamientos de
modelado de los objetos en su software utilizando el estado UML y los
diagramas de secuencia UML.
La complejidad del diseño se aplica tanto a las clases como a los métodos
dentro de ellas. Esta sección usará el término módulo para referirse a unidades
de programa que contienen clases y los métodos dentro de ellas.
Acomplamiento
La facilidad es cuán obvias son las conexiones entre el módulo y los demás. Las
conexiones deben ser fáciles de realizar sin necesidad de comprender las
implementaciones de otros módulos, con fines de acoplamiento.
La flexibilidad indica cuán intercambiables son los otros módulos para este
módulo. Otros módulos deberían ser fácilmente reemplazables por algo mejor
en el futuro, con fines de acoplamiento.
Cohesión
La cohesión se centra en la complejidad dentro de un módulo y representa la
claridad de las responsabilidades de un módulo. Al igual que la complejidad, la
cohesión puede funcionar entre dos extremos: alta cohesión y baja cohesión.
Un módulo que realiza una tarea y nada más, o que tiene un propósito claro,
tiene una alta cohesión. Un buen diseño tiene una alta cohesión. Por otro lado,
si un módulo encapsula más de un propósito, si se debe romper un
encapsulamiento para comprender un método, o si el módulo tiene un
propósito poco claro, tiene baja cohesión. Un mal diseño tiene baja cohesión.
Si un módulo tiene más de una responsabilidad, es una buena idea dividir el
módulo.
Un interés o concepto es una noción muy general: es todo lo que importa para
proporcionar una solución a un problema. La separación de conceptos se trata
de mantener separadas los diferentes conceptos en su diseño. Cuando se
diseña software, se deben abordar diferentes conceptos en diferentes partes
del software.
Es importante señalar que los límites de cada clase no siempre serán evidentes
en la práctica. Decidir cómo abstraer, encapsular, descomponer y generalizar
para abordar las muchas inquietudes de un problema dado es el núcleo del
diseño de software modular.
public SmartPhone() { … }
Este código tiene una clase SmartPhone con atributos llamados camera y
phone, y comportamientos asociados. Este sistema tiene una baja cohesión, ya
que hay comportamientos que no están relacionados entre sí. No es necesario
encapsular los comportamientos de la cámara con los comportamientos del
teléfono para que la cámara haga su trabajo. Los componentes tampoco
ofrecen modularidad. Por ejemplo, no es posible acceder a la cámara o al
teléfono por separado si se construye otro sistema que requiere solo uno u
otro. La cámara tampoco podría reemplazarse con una cámara diferente, o
incluso con un objeto diferente, sin eliminar el código de la cámara por
completo de esta clase.
La clase SmartPhone necesita ser más cohesiva y cada componente del teléfono
inteligente debe tener una funcionalidad distintiva. Usando la separación de
preocupaciones, podemos identificar que la clase SmartPhone tiene dos
preocupaciones:
1. Actuar como un teléfono tradicional.
2. Para tomar fotos usando la cámara incorporada.
Con las inquietudes identificadas, es posible separarlas en sus propias clases
más cohesivas y encapsular todos los detalles sobre cada inquietud en clases
funcionalmente distintas e independientes.
La clase SmartPhone hará referencia a instancias de las nuevas clases para que
el teléfono inteligente pueda actuar como coordinador de la cámara y el
teléfono. Esto permitirá que nuestro smartphone proporcione acceso a todos
los comportamientos de la cámara y del teléfono, sin tener que saber cómo se
comporta cada componente.
Usando un diagrama de clase UML, así es como se vería el nuevo diseño para el
sistema SmartPhone:
Con este rediseño, la clase SmartPhone brinda las funciones tanto de la cámara
como del teléfono. Sin embargo, las clases de cámara y teléfono están
separadas, por lo que sus funcionalidades están ocultas entre sí, pero aún se
agregan en la clase SmartPhone. También hay un constructor de SmartPhone
con una cámara y un teléfono como parámetros. Es posible crear una nueva
instancia de la clase SmartPhone pasando instancias de clases que
implementaron las interfaces ICamera e IPhone. Quién crea los objetos
apropiados de teléfono y cámara se deja como una responsabilidad separada,
ya que la clase SmartPhone en realidad no necesita saber esto. Finalmente, la
clase SmartPhone tiene métodos que reenvían las responsabilidades de usar la
cámara y el teléfono a estos objetos.
Esto crea un diseño modular: si el diseño requiere que las clases de cámara o
teléfono se cambien por otra cosa, entonces no es necesario tocar el código de
la clase SmartPhone. El código simplemente se cambia para instanciar el
SmartPhone y sus partes.
Ocultación de información
Un sistema bien diseñado está bien organizado, lo que se logra con la ayuda de
una serie de principios de diseño. Esta lección investigará más a fondo el
concepto de acceso a la información. No todos los componentes de un sistema
de software necesitan saber todo lo demás. Los módulos solo deben tener
acceso a la información que necesitan para hacer su trabajo. Limitar la
información a los módulos para que solo se necesite la cantidad mínima de
información para usarlos correctamente y "ocultar" todo lo demás se hace
mediante la ocultación de información.
Por lo tanto, una buena regla oro, es que las cosas que pueden cambiar, como
los detalles de implementación, deben ocultarse, y las cosas que no cambian,
como las suposiciones, se revelan a través de las interfaces.
• Público
• Protegido
• Por defecto
• Privado
Protegidos
Los atributos y métodos que están protegidos no son accesibles para todas las
clases del sistema. En cambio, solo están disponibles para la clase encapsulada,
todas las subclases y clases dentro del mismo paquete. Los paquetes son los
medios por los cuales Java organiza las clases relacionadas en un solo espacio
de nombres.
Predeterminado
Un modificador de acceso predeterminado solo permite el acceso a los
atributos y métodos de las subclases o clases que forman parte del mismo
paquete o encapsulación. Este modificador de acceso también se conoce como
acceso sin modificador porque no es necesario declararlo explícitamente en el
código.
Privados
Los atributos y métodos que son privados no son accesibles para ninguna otra
clase que no sea la propia clase encapsuladora. Esto significa que no se puede
acceder a estos atributos directamente y que ninguna otra clase puede invocar
estos métodos.
Integridad Conceptual
La integridad conceptual es un concepto relacionado con la creación de
software consistente. La integridad conceptual implica tomar decisiones sobre
el diseño y la implementación de un sistema de software, por lo que incluso si
varias personas trabajan en él, parecería cohesivo y consistente como si solo
una mente guiara el trabajo. Esto generalmente se logra a través de un acuerdo
para usar ciertos principios y convenciones de diseño para crear el sistema.
Las revisiones de código son exámenes sistemáticos del código escrito. Estos
son similares a la revisión por pares en la escritura académica. Los
desarrolladores revisan el código, línea por línea y descubren problemas en el
código de los demás. Esto ayuda a identificar errores en el software, pero
también ayuda a crear coherencia entre el código de diferentes
desarrolladores.
El uso de ciertos principios de diseño y construcciones de programación ayuda
a mantener la integridad conceptual. En particular, las interfaces de Java son
una construcción que puede lograr esto. Una interfaz define un tipo con un
conjunto de comportamientos esperados. La implementación de clases de esa
interfaz tendrá estos comportamientos en común. Esto crea coherencia en el
software y aumenta la integridad conceptual.
¿SABÍAS?
Principios de generalización
En el módulo anterior se revisó los cuatro principios de diseño de abstracción,
encapsulación, descomposición y generalización. Estos principios ayudan a
guiar las elecciones que se deben hacer al diseñar un sistema orientado a
objetos.
Sin embargo, algunas decisiones de diseño son más fáciles de tomar que otras.
La generalización y la herencia son algunos de los temas más difíciles de
dominar en la programación y el modelado orientados a objetos.
En Java, hay una biblioteca con una clase Stack, que es un ejemplo de mala
herencia. Una pila se entiende como una estructura de datos de tipo primero
en entrar, último en salir, con una pequeña cantidad de comportamientos bien
definidos como mirar, abrir y empujar. Pero, la clase Java Stack hereda de una
superclase Vector. Esto significa que la clase Stack puede devolver un elemento
en un índice específico, recuperar el índice de un elemento e incluso insertar
un elemento en un índice específico. Estos no son comportamientos que
normalmente se esperan de una pila.
Los diagramas de secuencia pueden ayudarlo a visualizar las clases que creará
en su software y determinar las funciones que deberán escribirse. También
puede ilustrar problemas en su sistema que antes eran desconocidos.
Dentro del bucle, el televidente presiona la flecha hacia arriba o hacia abajo en
el control remoto para cambiar de canal. Esto envía un mensaje al control
remoto. Luego, el control remoto envía un mensaje al televisor con esta acción.
El televisor cambia el canal y se lo muestra al espectador. La secuencia final se
verá de la siguiente manera:
Los diagramas de secuencia son una técnica útil para ayudar a crear programas
limpios y bien diseñados.
Los diagramas de estado UML son otro tipo de diagrama UML. Son una técnica
utilizada para describir cómo se comportan y responden los sistemas. Siguen
los estados de un sistema o de un solo objeto y muestran cambios entre los
estados a medida que ocurren una serie de eventos en el sistema.
Los diagramas de estado pueden ser útiles para ayudar a determinar los
eventos que pueden ocurrir durante la vida útil de un objeto, como las
diferentes entradas del usuario, y cómo debe comportarse ese objeto cuando
ocurren estos eventos, como verificar condiciones y realizar acciones. A veces
puede ser más fácil ver los cambios de estado en un diagrama, en lugar de leer
el código fuente.
Comprobación de modelo
Además de comprender las técnicas para diseñar un sistema de software, es
importante conocer las técnicas para verificar el sistema. Algunas de estas
técnicas incluyen pruebas unitarias, pruebas beta y simulaciones. Otra de estas
técnicas es la comprobación de modelos, que es una verificación sistemática
del modelo de estado de su sistema en todos sus estados posibles. La
verificación de modelos ayuda a encontrar errores que otras pruebas no
pueden.
En la verificación de modelos, verifica todos los diversos estados del software
para tratar de identificar cualquier error, simulando diferentes eventos que
cambiarían los estados y las variables del software. Esto ayudará a exponer
cualquier falla al notificarle cualquier violación de las reglas que ocurra en el
comportamiento del modelo estatal. Por lo general, las comprobaciones de
modelos se realizan mediante software de comprobación de modelos. Hay
diferentes tipos de software disponibles para tales pruebas, algunos de los
cuales son gratuitos y están disponibles para desarrolladores que utilizan
diferentes idiomas. La verificación del modelo generalmente se realiza durante
la prueba del software.
La verificación del modelo ayuda a garantizar no solo que el software esté bien
diseñado, sino también que el software cumpla con las propiedades y el
comportamiento deseados, y funcione según lo previsto.