A5.C Estructura y cuándo usar patrones¶
Los patrones de estructura tratan de cómo se combinan clases y objetos para formar piezas mayores.
Decorador: añadir sin modificar¶
Un decorador añade comportamiento a un objeto envolviéndolo, sin cambiar su clase ni crear una subclase para cada combinación. Sin él, «café», «café con leche», «café con azúcar» y «café con leche y azúcar» serían cuatro clases (y el doble con cada ingrediente nuevo). Con decoradores son tres, y las combinaciones salen de apilarlos.
Cada decorador guarda otra bebida (_base), a la que delega y a la que le añade su parte. Como todas tienen descripcion y precio, se pueden apilar en cualquier número y orden. (Python no necesita una interfaz común: basta con que los objetos tengan las mismas propiedades.) No lo confundas con los decoradores @ del lenguaje: son una función que envuelve a otra, una idea parecida aplicada a funciones.
# Decorador: un objeto que ES de un tipo y además TIENE otro de ese tipo dentro, al que le añade algo
# sin tocar su clase. Se pueden apilar tantos como se quiera.
# (No confundir con los «decoradores» @ de Python, que son una función que envuelve a otra: la idea es parecida.)
class Cafe:
descripcion = "café"
precio = 150 # en céntimos
class ConLeche:
def __init__(self, base):
self._base = base
@property
def descripcion(self):
return f"{self._base.descripcion} con leche"
@property
def precio(self):
return self._base.precio + 40
class ConAzucar:
def __init__(self, base):
self._base = base
@property
def descripcion(self):
return f"{self._base.descripcion} con azúcar"
@property
def precio(self):
return self._base.precio + 10
def mostrar(bebida):
print(f"{bebida.descripcion}: {bebida.precio} céntimos")
bebida = Cafe()
mostrar(bebida)
bebida = ConLeche(bebida)
mostrar(bebida)
bebida = ConAzucar(bebida)
mostrar(bebida)
Salida:
café: 150 céntimos
café con leche: 190 céntimos
café con leche con azúcar: 200 céntimos
Cada decorador añade su parte al resultado del anterior: el precio sube 40 con la leche y 10 con el azúcar, y la descripción se va completando. Y el orden importa cuando la operación no es conmutativa (lo verás en el ejercicio A5.6).
Otros dos que conviene conocer¶
| Patrón | Para qué sirve | Ejemplo |
|---|---|---|
| Adaptador (adapter) | Hacer que una clase con una interfaz distinta encaje donde se espera otra, sin modificarla | Una biblioteca antigua devuelve grados Fahrenheit y tu código espera Celsius: el adaptador las conecta |
| Fachada (facade) | Ofrecer una interfaz sencilla delante de un subsistema complicado | Un único reproducir(archivo) que por dentro abre, decodifica y envía a los altavoces |
Los dos hacen lo mismo en el fondo: esconden una complicación detrás de algo que encaja mejor. La diferencia es qué esconden: el adaptador, una incompatibilidad; la fachada, muchas piezas.
¿Qué patrón uso?¶
| Tengo este problema | Patrón |
|---|---|
| Debe haber un único objeto de algo | Singleton (con mucho cuidado) |
| Decido qué clase crear según un dato | Fábrica |
| Construir un objeto con muchos datos opcionales | Builder |
| Quiero cambiar un algoritmo sin tocar la clase | Estrategia |
| Debo avisar a quien quiera enterarse | Observador |
| Quiero añadir comportamiento combinable | Decorador |
| Una clase no encaja donde la necesito | Adaptador |
| Un subsistema es complicado de usar | Fachada |
Cuándo NO usar un patrón¶
La «patitis»
Los patrones se descubren después de ver el problema, no antes. Un programa pequeño con cinco patrones no es más profesional: es más difícil de leer. Empieza con el código más simple que funcione, y cuando notes que repites una estructura o que cambiar algo obliga a tocar muchos sitios, entonces aplica el patrón que lo arregla.
| Señal | Qué hacer |
|---|---|
| Una clase con un solo método, creada «por si acaso» | Quítala: una función basta |
| Una fábrica con una sola clase | No la necesitas todavía |
| Tres capas de decoradores para algo que cambia una vez | Un if es más claro |
| Un patrón que tu lenguaje ya resuelve con una función | Usa la función (estrategia, observador, builder) |
Como has visto en Python, muchos patrones clásicos se reducen a funciones cuando el lenguaje las trata como valores (A1). Los patrones no son un fin: son un vocabulario común y unas soluciones probadas, y lo importante es el principio de fondo, que ya conoces: depender de abstracciones y separar lo que cambia de lo que no (SOLID).
Errores frecuentes¶
| Error | Cómo evitarlo |
|---|---|
| Aplicar un patrón porque «queda bien» | Aplícalo cuando resuelva un problema que ya tienes |
| Confundir el nombre con la implementación | El patrón es la idea: cada lenguaje la escribe a su manera |
| Apilar decoradores y perder la cuenta de lo que hace cada uno | Pocos decoradores, con nombres claros |
| Copiar la estructura de un libro de otro lenguaje tal cual | Usa lo que tu lenguaje ya ofrece (funciones, object, parámetros con nombre) |
Para practicar¶
El ejercicio A5.6 pide decoradores de texto y comprobar que el orden importa. Para ver la misma idea en los otros lenguajes: Pasar de uno a otro.