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
- Un decorador y sus componentes no son idénticos
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
- El comportamiento de un objeto depende de su estado
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.