Patrones de Diseño: Guía Completa con Ejemplos y Aplicaciones


SRP: Principio de Responsabilidad Única

Una clase debería tener un único motivo de cambio. Si podemos pensar en más de un motivo por el que la clase debería cambiar, dicha clase tiene más de una responsabilidad.

OCP: Principio de Abierto-Cerrado

Las entidades software (clases, módulos, funciones…) deberían estar abiertas para la extensión, pero cerradas para la modificación.

Cliente → (Interface) Servidor → ServidorImpl

LSP: Principio de Sustitución de Liskov

Los subtipos deben poder reemplazar a cualquiera de sus tipos base. Los objetos de un programa deberían ser reemplazables por instancias de sus subtipos sin alterar el correcto funcionamiento de un programa.

DIP: Principio de Inversión de Dependencias

Los módulos de alto nivel no deberían depender de módulos de bajo nivel. Ambos deben depender de abstracciones. Las abstracciones no deben depender de detalles. Son los detalles los que deben depender de abstracciones. TL;DR: Hay que depender de abstracciones, no de implementaciones concretas. Por ejemplo, si en un controlador usamos un factory que internamente utiliza el patrón strategy para generar un objeto, en esa operación tenemos 3 capas de abstracción, siendo 1: controlador, 2: factory, 3: strategy, en ese orden de abstracción (el strategy es el menos abstracto en esta lista). El principio dice entonces que controlador no debe depender de factory ni factory de strategy para funcionar.

Button -> Lamp
SOL: Button -> Switch <<Interface>> +turnOn(); +turnOf(); <- Lamp

ISP: Principio de Segregación de Interfaces

Es mejor muchas interfaces específicas para cada cliente que una sola interfaz de propósito general. Los clientes no deberían depender de métodos que no usan.

Síntomas de un Diseño Pobre

  • Rigidez: Es difícil de cambiar
  • Fragilidad: Es fácil que falle
  • Inmovilidad: Es difícil de reutilizar
  • Viscosidad: Es difícil hacer lo correcto
  • Complejidad innecesaria: Sobrediseño
  • Repetición innecesaria: «Copiar y pegar»
  • Opacidad: Es complejo averiguar su intención

STRATEGY

Define una familia de algoritmos, encapsula cada uno y los hace intercambiables. Permite que el algoritmo varíe de forma independiente a los clientes que lo usan.

Aplicabilidad

  • Permite configurar una clase con un comportamiento determinado de entre varios
  • Se necesitan distintas variantes de un algoritmo
  • Los distintos comportamientos de una clase aparecen como múltiples sentencias condicionales.

Consecuencias

  • Define familias de algoritmos relacionados
  • Es una alternativa a la herencia
  • Elimina las múltiples sentencias condicionales
  • El cliente puede elegir entre varias implementaciones
  • Los clientes deben conocer las distintas estrategias
  • Puede complicarse la comunicación entre el contexto y las estrategias
  • Crece el número de objetos

FACTORY METHOD

Define una interfaz para crear un objeto, pero deja que sean las subclases quienes decidan la clase del objeto a crear.

Aplicabilidad

  • Úsese cuando:
    • Una clase no puede anticipar la clase de objetos que debe crear
    • Una clase quiere que sus subclases especifiquen los objetos a crear
    • Hay clases que delegan responsabilidades en una o varias subclases, y queremos localizar el conocimiento de qué subclase es el delegado.

Consecuencias

  • Elimina la necesidad de enlazar clases específicas de la aplicación en el código – Sólo maneja la interfaz Product
    • Por lo que permite añadir cualquier clase ConcreteProduct definida por el usuario
  • Inconveniente:
    • Tener que crear una subclase de Creator en los casos en los que ésta no fuera necesaria de no aplicar el patrón

ABSTRACT FACTORY

Define una interfaz para crear familias de objetos relacionados sin especificar sus clases concretas.

Aplicabilidad

Si queremos que una aplicación se aproveche de ello y sea portable, no podrá crear directamente objetos de esas clases específicas

Consecuencias

  • Aísla las clases concretas – Los clientes manipulan los productos únicamente a través de sus interfaces abstractas, gracias a que las clases de productos concretos están encapsuladas en cada fábrica concreta, no aparecen en el código
  • Permite intercambiar fácilmente familias de productos – Basta con cambiar una única clase, en un único sitio: la fábrica concreta
  • Promueve la consistencia entre los productos – Sólo se pueden usar conjuntamente los objetos de cada familia
  • Dificulta añadir nuevos tipos de productos – Hay que cambiar la interfaz de la fábrica abstracta y por tanto implementar el nuevo método en todas sus subclases

DECORATOR

Permite añadir responsabilidades a un objeto dinámicamente. Los decoradores proporcionan una alternativa flexible a la herencia para extender la funcionalidad.

Aplicabilidad

  • Para añadir responsabilidades a otros objetos dinámicamente y de forma transparente
  • Cuando no se puede heredar o no resulta práctico (explosión de subclases para permitir cada combinación posible)

Consecuencias

  • Ventajas
    • Más flexibilidad que la herencia estática
    • Evita que las clases de arriba de la jerarquía estén repletas de funcionalidades
      • En vez de definir una clase compleja para tratar de dar cabida a todas ellas, la funcionalidad se logra añadiendo decoradores a una clase simple
  • Inconvenientes
    • Un decorador y sus componentes no son idénticos
      • Desde el punto de vista de la identidad de objetos
    • Muchos objetos pequeños
      • El sistema puede ser más difícil de aprender y de depurar

ADAPTER

Convierte la interfaz de una clase en otra que es la que esperan los clientes. Permite que trabajen juntas clases que de otro modo no podrían por tener interfaces incompatibles.

Aplicabilidad

  • Queremos usar una clase existente, y ésta no tiene la interfaz (es decir, el tipo) que necesitamos
  • Queremos crear una clase reutilizable que coopere con clases con las que no está relacionada – Que no tendrán, por tanto, interfaces compatibles
  • (Sólo la versión de objetos) Necesitamos usar varias subclases existentes pero sin tener que adaptar su interfaz creando una nueva subclase de cada una – Un adaptador de objetos puede adaptar la interfaz de su clase padre

Consecuencias

  • Las versiones de clases y de objetos de este patrón tienen diferentes ventajas e inconvenientes
  • Un adaptador de clases.
  • Un adaptador de objetos – Permite que un único adaptador funcione no sólo con un objeto de la clase adaptada, sino de cualquiera de sus subclases

COMPOSITE

Permite componer objetos en estructuras arbóreas para representar jerarquías de todo-parte, de modo que los clientes puedan tratar a los objetos individuales y a los compuestos de manera uniforme.

Aplicabilidad

  • Representar jerarquías de parte-todo
  • Que los clientes traten por igual los objetos individuales y los compuestos

Consecuencias

  • Permite jerarquías de objetos tan complejas como se quiera – Allá donde el cliente espere un objeto primitivo, podrá recibir un compuesto y no se dará cuenta
  • Simplifica el cliente – Al eliminar el código para distinguir entre unos y otros
  • Se pueden añadir nuevos componentes fácilmente
  • Como desventaja, podría hacer el diseño demasiado general

COMMAND

Encapsula una petición dentro de un objeto, permitiendo parametrizar a los clientes con distintas peticiones, encolarlas, guardarlas en un registro de sucesos o implementar un mecanismo de deshacer/repetir.

Aplicabilidad

  • Úsese el patrón Command cuando se quiera:
    • Parametrizar objetos con una determinada acción
  • Que la acción a realizar y el objeto que lanza la petición tengan ciclos de vida distintos
  • Permitir deshacer/repetir («undo/redo»)
  • Guardar todas las operaciones ejecutadas en un registro («log»)
  • Usar transacciones

Consecuencias

  • Desacopla el objeto que llama a la operación del que sabe cómo llevarla a cabo
  • Son ciudadanos «de primera clase» (objetos)
  • Se pueden ensamblar (Composite)
  • Resulta sencillo añadir nuevas acciones, al no tener que tocar las clases existentes

STATE

Permite a un objeto alterar su comportamiento cuando cambia su estado interno. Parecerá como si el objeto hubiera cambiado su clase.

Aplicabilidad

  • Úsese en los siguientes casos:
    • El comportamiento de un objeto depende de su estado
      • Y éste puede cambiar en tiempo de ejecución
    • Las operaciones tienen sentencias condicionales anidadas que tratan con los estados

Consecuencias

  • Localiza el comportamiento específico del estado y lo aísla en un objeto – Se pueden añadir nuevos estados y transiciones fácilmente simplemente definiendo nuevas subclases de State
  • Hace explícitas las transiciones entre estados

TEMPLATE

Define el esqueleto de un algoritmo en una operación, difiriendo algunos pasos hasta las subclases. Permite que éstas redefinan ciertos pasos del algoritmo sin cambiar la estructura del algoritmo en sí.

Aplicabilidad

  • Para implementar las partes de un algoritmo que no cambian y dejar que las subclases implementen aquéllas otras que pueden variar
  • Como motivo de factorizar código, cuando movemos cierto código a una clase base común para evitar código duplicado
  • Para controlar el modo en que las subclases extienden la clase base – (Dejando que sea sólo a través de unos métodos de plantilla dados)

OBSERVER

Define una dependencia uno-a-muchos entre objetos, de modo que cuando un objeto cambia su estado, todos los demás objetos dependientes se modifican y actualizan automáticamente

Aplicabilidad

  • Una abstracción tiene dos aspectos, uno de los cuales depende del otro
  • Un cambio en un objeto requiere que cambien otros
  • Un objeto necesita notificar a otros cambios en su estado sin hacer presunciones sobre quiénes son dichos objetos.

Consecuencias

  • Permite variar objetos observados y observadores independientemente
  • Acoplamiento abstracto entre Subject y Observer
  • No se especifica el receptor de una actualización
  • Actualizaciones inesperadas

VISITOR

Representa una operación a realizar sobre una estructura de objetos. Permite definir nuevas operaciones sin modificar las clases de los elementos sobre los que opera.

Aplicabilidad

  • Debería aplicarse el patrón Visitor cuando:
    • Una estructura de objetos contiene muchas clases de objetos con diferentes interfaces, y queremos realizar operaciones sobre esos elementos que dependen de su clase concreta
    • Se necesitan realizar muchas operaciones distintas y no relacionadas sobre objetos de una estructura de objetos, y queremos evitar «contaminar» sus clases con dichas operaciones
  • Debería aplicarse el patrón Visitor cuando (cont.):
    • Las clases que definen la estructura de objetos rara vez cambian, pero muchas veces queremos definir nuevas operaciones sobre la estructura

Consecuencias

  • El visitante facilita añadir nuevas operaciones – Podemos definir una nueva operación sobre una estructura simplemente añadiendo un nuevo visitante
  • Un visitante agrupa operaciones relacionadas y separa las que no lo están
  • Es difícil añadir nuevas clases de elementos concretos

PROTOTYPE

Especifica los tipos de objetos a crear usando una instancia prototípica, y crea nuevos objetos copiando dicho prototipo.

Aplicabilidad

  • Úsese el patrón Prototype cuando un sistema no pueda (o no deba) conocer cómo se crean, componen y representan los productos, y además se da alguna de estas circunstancias:
    • las clases a instanciar son definidas en tiempo de ejecución
    • para evitar construir una jerarquía paralela de factorías de productos
    • cuando las instancias de una clase puedan tener sólo unos pocos posibles estados

Consecuencias

  • Como el patrón Abstract Factory oculta las clases concretas de producto al cliente
  • Además permite:
    • Añadir y eliminar productos dinámicamente (en tiempo de ejecución) Especificar nuevos objetos modificando valores de sus propiedades
    • Especificar nuevos objetos variando su estructura
    • Reduce las subclases
  • Inconvenientes:
    • La implementación de la operación de clonación puede no ser fácil

DIFERENCIAS

DECORATOR – ADAPTER – STRATEGY

Patrones relacionados

  • Adapter – El decorador sólo cambia las responsabilidades del objeto, no su interfaz
  • Composite – Un decorador puede verse como un «composite» de un solo componente – Pero el decorador añade responsabilidades adicionales (no está pensado para la agregación de objetos)
  • Strategy – El decorador cambia la «piel» del objeto; una estrategia cambia sus «tripas»

PROTOTYPE – FACTORIES

Prototype permite al objeto crear objetos personalizados sin necesidad de conocer su clase o los detalles de como han sido creados. En este caso se parece al Factory Method pero la diferencia se aisla en que el Factory Method se enfoca en crear un objeto de un tipo no existente como nuevo y el Prototype usa la clase en sí, la clase derivada.

Dejar un Comentario

Tu dirección de correo electrónico no será publicada. Los campos obligatorios están marcados con *