Patrones de Diseño de Software GRASP: Principios y Aplicaciones

Enviado por Chuletator online y clasificado en Informática y Telecomunicaciones

Escrito el en español con un tamaño de 21 KB

GRASP: Patrones de Asignación de Responsabilidades Generales

  • General Responsibility Assignment Software Patterns.
  • Definen nueve principios básicos del diseño Orientado a Objetos (objetos y asignación de responsabilidades).

Patrones GRASP Básicos

Creador / Creator

Problema: ¿Quién crea una instancia de A?

Solución: Asignar a la clase B la responsabilidad de crear una instancia de la clase A si se cumple alguna de las siguientes condiciones (cuántas más, mejor):

  • B contiene o tiene agregado A.
  • B graba A.
  • B usa de forma cercana A.
  • B tiene la información para inicializar A.

Discusión:

  • Creator busca el objeto creador que requiere estar conectado al objeto creado en cualquier momento.
  • Soporta bajo acoplamiento.
  • Es común que el creator haya de buscar la clase que tiene los datos de inicialización que serán pasados por parámetro.

Contraindicaciones: La creación podría ser compleja (creación condicional, reciclamiento de instancias...). En este caso, se ha de hacer uso del patrón Factory (GoF).

Beneficios:

Favorecer el bajo acoplamiento, lo que implica:

  • Bajo nivel de dependencias de mantenimiento.
  • Alta oportunidad de reuso.

Patrones y principios relacionados:

  • Bajo acoplamiento.
  • Patrón factoría (concreta o abstracta).
  • Whole-Part: describe un patrón para definir objetos agregados que soportan la encapsulación de componentes.

Experto (en información) / Information Expert

Problema: ¿Cuál es el principio general de asignación de responsabilidades a objetos?

Solución: Asignar responsabilidades a la clase que tiene la información necesaria para satisfacerla.

Discusión:

  • Los objetos hacen tareas relacionadas con la información que poseen. La realización de la tarea muchas veces requerirá de información de distintas clases (aplicado en cascada).
  • Principio de animación (caricatura animada): los objetos en la programación Orientada a Objetos son objetos vivos o animados que tienen responsabilidades y realizan cosas.
  • Tal como en el mundo real, las tareas las hacen quienes tienen la información para realizarlas.

Contraindicaciones: Problemas de cohesión y acoplamiento.

Beneficios:

  • Mantiene encapsulamiento de información (bajo acoplamiento).
  • Motiva clases ligeras, distribuyendo responsabilidades (alta cohesión).

Principios relacionados:

  • Bajo acoplamiento.
  • Alta cohesión.

Conocido como:

  • “Place responsibilities with data”.
  • “That which knows, does”.
  • “Do it myself”.
  • “Put services with the attributes they work on”.

Bajo Acoplamiento / Low Coupling

Problema: ¿Cómo reducir el impacto del cambio?

Solución: Asigna responsabilidades de forma que el acoplamiento sea mínimo.

Discusión:

  • No hay una medida específica para el acoplamiento, pero en general, clases genéricas y candidatas al reuso deberían asegurar bajo acoplamiento.
  • Siempre habrá algo de acoplamiento entre objetos (ya que existe colaboración entre ellos).

Contraindicaciones:

  • Rara vez es problema tener alto acoplamiento en elementos estables y de uso amplio.
  • No vale la pena desgastarse en disminuir el acoplamiento cuando no hay una motivación real.

Beneficios:

  • No existen efectos colaterales debido a cambios en otros componentes.
  • Simple de entender aisladamente.
  • Muy conveniente para el reuso.
  • La mayoría de patrones favorecen el bajo acoplamiento.

Patrones relacionados: Protected Variation.

Background: El acoplamiento y la cohesión son principios fundamentales en el diseño, y la mayoría de patrones favorecen el bajo acoplamiento.

  • Acoplamiento: medida que indica cómo de fuertemente un elemento está conectado, tiene conocimiento o depende de otros elementos.
  • Alto acoplamiento:
    • La clase depende de muchas clases.
    • Cambios en las clases relacionadas fuerzan cambios en la clase afectada.
    • La clase afectada es más difícil de entender por sí sola.
    • La clase afectada es más difícil de reutilizar, porque requiere la presencia adicional de las demás clases de las que depende.
  • Tipos de acoplamiento:
    • Definición de atributos: x tiene un atributo que refiere a una instancia de y.
    • Definición de interfaces de métodos: un parámetro o una variable local de tipo y se encuentra en un método de x.
    • Definición de subclases: x es una subclase de y.
    • Definición de tipos: x implementa la interfaz y.

Controlador / Controller

Problema: ¿Cuál es el primer objeto por debajo de la interfaz de usuario que debe recibir y coordinar el mensaje del usuario?

Solución: Asignar responsabilidades al objeto que representa una de estas opciones:

  • Representa el sistema completo (control fachada).
  • Representa un escenario de un caso de uso donde la operación se encuentra (control de sesión).

Discusión:

  • La capa interfaz no debe contener lógica de aplicación.
  • El controlador en sí no realiza muchas tareas, sólo delega en otros.
  • El controlador fachada será el que se encargue de todos los eventos, por lo que es un único controlador.
  • El controlador sesión sólo de alguno, por lo que habrá varios controladores pequeños.

Beneficios:

  • Incrementa la posibilidad del reuso y la conexión de interfaces.
  • Permiten implementar una secuencia de operaciones o mantener el estado del caso de uso.

Patrones relacionados:

  • Command.
  • Facade.
  • Layers: a POSA pattern. Placing domain logic in the domain layer rather than the presentation layer is part of the Layers.
  • Pure Fabrication: a GRASP pattern. Arbitrary creation of designer, not a software class whose name is inspired by de domain model. A use case controller is a kind of Pure Fabrication.

Alta Cohesión / High Cohesion

Problema: ¿Cómo mantener los objetos centrados, entendibles y manejables, y que además soporte el bajo acoplamiento?

Solución: Asignar responsabilidades de forma que la cohesión se mantenga alta. Lo mejor es evaluar alternativas.

Discusión: Regla general: Una clase con alta cohesión tiene un número relativamente bajo de operaciones, con funcionalidad altamente relacionada y no hace demasiado trabajo, colabora y delega.

Contraindicaciones: Existen pocos casos que contraindiquen la alta cohesión:

  • Agrupar responsabilidades o código en una sola clase o componente para simplificar el mantenimiento por una sola persona.
  • Servidor de objetos distribuidos, debido a las implicaciones de overhead o performance asociadas a objetos remotos y a la comunicación remota. Relativo al patrón interfaz remota de granularidad gruesa.

Beneficios:

  • Aumento de la claridad y fácil comprensión del diseño.
  • Mantenimiento y mejoras simplificadas.
  • Bajo acoplamiento.

Cohesión: medida de cómo de fuertemente se relacionan y enfocan las responsabilidades de un elemento.

  • Fina granularidad en el reuso, la funcionalidad aumenta porque una clase cohesiva puede ser usada para propósitos muy específicos.
  • Baja cohesión:
    • La clase realiza muchas actividades poco relacionadas o realiza demasiado trabajo.
    • Problemas:
      • Difíciles de entender.
      • Difíciles de usar.
      • Difíciles de mantener.
      • Delicados: fácilmente afectables por cambios.
  • Grados de cohesión:
    • Muy baja: clase responsable de muchas cosas en muchas áreas distintas.
    • Baja: clase responsable de una tarea compleja en un área funcional.
    • Alta: clase que tiene responsabilidades moderadas en un área funcional y colabora con otras clases para realizar una tarea.
    • Moderada: clase que tiene pocas responsabilidades en varias áreas funcionales, estando éstas relacionadas lógicamente con la clase pero no entre ellas.

Patrones GRASP Avanzados

Polimorfismo / Polymorphism

Problema: ¿Cómo manejar alternativas basadas en el tipo? ¿Cómo crear componentes software para conectar?

  • Alternativas basadas en el tipo: usando sentencias if-else o case, cuando lleguen nuevas variaciones serán mucho más complicadas de controlar.
  • Componentes conectados: cómo reemplazar un componente con otro sin que afecte al cliente.

Solución: Cuando alternativas o comportamientos relacionados varían por el tipo, asigna la responsabilidad del comportamiento usando “operaciones polimórficas” (usa el mismo nombre a servicios en diferentes objetos) a los tipos para los cuales el comportamiento varía.

Discusión: El uso de polimorfismo para la asignación de responsabilidades hace que el sistema sea más abierto a manejar nuevos cambios similares. En la mayoría de lenguajes, aplicar polimorfismo implica el uso de clases abstractas. ¿Cuándo debe considerarse el uso de interfaces? La respuesta es introducirlas cuando quieres soportar el polimorfismo sin comprometerse a una jerarquía de clases. Una regla general usando jerarquía de clases, es que la clase base implemente una interfaz con las signaturas de los métodos públicos de la misma.

Contraindicaciones: No utilizar el polimorfismo si no se esperan posibles cambios, ya que el polimorfismo conlleva tiempo y esfuerzo.

Beneficios:

  • Extensiones para el manejo de variaciones son más fáciles de añadir.
  • Se pueden introducir nuevas implementaciones sin necesidad de afectar al cliente.

Patrones relacionados:

  • Protected Variation.
  • Muchos patrones GoF: adapter, command, composite, proxy, state and strategy.

También conocido como: Choosing message, Don’t ask “what kind?”

Fabricación Pura / Pure Fabrication

Problema: ¿Qué objeto debería tener la responsabilidad cuando no se desean violar los principios de alta cohesión y bajo acoplamiento, pero las soluciones sugeridas como Expert no son apropiadas?

Solución: Asignar un conjunto de responsabilidades altamente cohesivas a una clase artificial que represente un concepto del dominio del problema. Pure Fabrication implica que nos inventamos totalmente algo que soporte la alta cohesión, el bajo acoplamiento y el reuso.

Este principio es el que se utiliza en la aplicación de Helper y clases Utility.

Discusión: El diseño de objetos se divide en dos grandes grupos:

  • Los diseñados por descomposición representacional.
  • Los diseñados por descomposición conductual.

Es el segundo caso el más común para objetos de fabricación pura. Las clases de este estilo son inventadas (ya que no representan a ningún objeto del dominio) para concentrar comportamiento común.

Beneficios:

  • Alta cohesión, porque las responsabilidades han sido refactorizadas en una clase de fina granularidad enfocada a un grupo de tareas muy específicas.
  • Aumenta el potencial del reuso por la fina granularidad de estas clases.

Contraindicaciones: En ocasiones se sobreutiliza por novatos a la hora de dividir el software, que lo dividen en términos de funciones, creado clases algorítmicas.

Patrones y principios relacionados:

  • Bajo acoplamiento.
  • Alta cohesión.
  • Expert Pattern, se basa en él a la hora de asignar responsabilidades.
  • Todos los GoF patterns, como adapter, command, strategy son pure fabrication.
  • Virtually.

Indirección / Indirection

Problema: ¿Dónde asignar responsabilidades para evitar acoplamiento directo entre dos o más cosas? ¿Cómo desacoplar objetos de forma que se mantenga un bajo acoplamiento y se soporte el reuso?

Solución: Asignar la responsabilidad a una clase intermedia que medie entre los otros componentes o servicios de forma que se acoplen directamente. De esa forma se crea una indirección entre los componentes.

Discusión: Un dicho en programación dice que “la mayoría de problemas en la computación se resuelven añadiendo otra capa de indirección”. Este tiene una particular relevancia a la hora del diseño Orientado a Objetos. Muchos patrones son especializaciones de la fabricación pura, otros también lo son de la indirección (adapter, facade, observer). Muchas fabricaciones puras son generadas a causa de la indirección.

Beneficios: El bajo acoplamiento. La principal motivación de uso de este patrón es mantener el bajo acoplamiento o desacoplar componentes o servicios.

Patrones y principios relacionados:

  • Protected Variations.
  • Low Coupling.
  • Muchos de los patrones GoF como adapter, bridge, facade, observer y mediator.
  • Muchas indirecciones son fabricaciones puras.

Protección de Variaciones / Protected Variations

Problema: ¿Cómo diseñar objetos, subsistemas y sistemas de forma que las variaciones o inestabilidad de los elementos no tengan un impacto negativo sobre otros?

Solución: Identificar los puntos de variación o inestabilidad (hot spots) y asignar responsabilidades para crear una interfaz estable a su alrededor. Interfaz se usa en el más extenso sentido de la palabra, no significa literalmente algo como una interfaz Java.

Discusión: Este es un principio de diseño software fundamental y muy importante. Casi todos los trucos o consejos de diseño software o arquitectural son especializaciones de este patrón.

Contraindicaciones: Hay que tener cuidado a la hora de predecir los puntos de variaciones o evolución. No se ha de trabajar más haciendo un diseño perfecto para cambios si estos no se van a producir. Se ha de encontrar un punto medio en el proyecto.

Beneficios:

  • Extensiones necesarias para nuevas variaciones son fáciles de añadir.
  • Se pueden introducir nuevas implementaciones sin afectar a los clientes.
  • Disminuye el acoplamiento.
  • El impacto y coste de los cambios disminuye.

Patrones y principios relacionados: Muchos de los patrones y principios de diseño son mecanismos de protección de variaciones.

Conocido como: Antes de ser establecido como un patrón, este concepto se conocía como ocultación de la información (hidding information) o principio de abierto/cerrado (open-closed principles).

  • Ocultación de la información: no se debe confundir con el concepto de encapsulación. Lo que se ha de ocultar es la información acerca del diseño de otros módulos, en los puntos difíciles o de posible cambio.
  • Principio de abierto/cerrado: los módulos han de ser abiertos a extensiones (adaptables), pero cerrados a modificaciones que afecten a los clientes. No siempre es posible conseguir este principio de manera completa, pero the most the better.

Principios de Diseño Motivados por PV

  • Encapsulación.
  • Diseñar operaciones para que consulten o modifiquen, pero no ambas cosas a la vez.
  • Separación del Modelo-Vista: los objetos del modelo no deberían conocer los objetos de presentación, para proporcionar bajo acoplamiento de capas inferiores a la superior, interfaz, que es la que más cambia.
  • Principio de sustitución de Liskov: Toda instancia de una clase derivada puede tomar el lugar una instancia de su clase base.
  • El principio open-closed.
  • Principio de inversión de dependencia:
    • Los módulos de alto nivel no deberían depender de módulos de bajo nivel. Todos deberían depender de abstracciones.
    • Las abstracciones no deberían depender de detalles, sino al contrario: los detalles deberían depender de las abstracciones.
    • Implicación: el acoplamiento entre objetos usados y que usan debe ser a nivel conceptual, sin tener en cuenta los detalles concretos de la implementación.
    • Llevarlo al extremo:
      • No deberían existir variables que referencien a clases concretas.
  • Ninguna clase debería derivar de una clase concreta.
  • Ningún método debería sobreescribir un método implementado de una de sus clases base.
  • El principio de segregación de interfaz (interface segregation principle): Los clientes no deben ser forzados a depender de interfaces que no usan. Es mejor tener muchas interfaces específicas que una muy general.
  • Principio de equivalencia de liberación y reuso (reuse/release equivalency principle): La granularidad del reuso ha de ser la misma que la de la liberación. Solo los componentes que son liberados pueden ser reusados de forma efectiva.
  • Principio del reuso común (common reuse principle): Las clases que no son reusadas juntas no han de estar agrupadas juntas.
  • Principio de cierre común (common closure principle): Las clases que cambian juntas, permanecen juntas.
  • Principio de abstracciones estables (stable abstraction principle): Cuanto más estable sea una categoría de clases, más razón para que éstas sean abstractas.
  • Principio de la mínima sorpresa (least astonishment principle): Cuando existen dos elementos de una interfaz conflictiva o ambigua, su comportamiento debe ser lo menos inesperado posible, ya que ese es normalmente el correcto.
  • Principio de jerarquías profundas y abstractas (deep abstract hierarchies principle): Las jerarquías de clases deben ser profundas y abstractas.
  • Principio de dependencias acíclicas (acyclic dependendies principle): No deben existir ciclos en los grafos de dependencias.
  • Principio de dependencias estables (stable dependencies principle): La dependencia debe ir en dirección a la estabilidad, es decir, un componente debe depender sólo de componentes más estables que él.
  • No hablar con extraños (don’t talk to strangers), ley de Demeter: Cada componente debe relacionarse con otros con los que esté relacionado de alguna forma (no son extraños). Así evitar acoplamientos con objetos indirectos y protegerse de cambios estructurales.
    • Al objeto this/self.
    • Un atributo de this.
    • Un elemento de una colección que es atributo de this.
    • Un parámetro del método.
    • Un objeto creado dentro del método.
  • ID to Objects: Convertir las claves de identificación en objetos lo antes posible. El objeto real flexibiliza la aplicación según el diseño crece.
  • Pasar objeto agregado como parámetro: cuando una operación requiere como parámetros objetos que están agregados dentro de otros, se ha de pasar el objeto agregado. Aumenta la flexibilidad del sistema.

Entradas relacionadas: