Tres recetas para problemas que se repiten
Un patrón de diseño es una solución probada a un problema que se repite al diseñar clases. Esta lección trata tres: la fábrica crea objetos, la estrategia cambia un algoritmo y el decorador añade funciones a un objeto.
Una tienda online aplica descuentos distintos, crea cada uno según un cupón y vende bebidas con extras. Cada problema ya tiene una receta conocida.
Al final de esta lección
Nota
Puedo aplicar una fábrica, una estrategia y un decorador.
Palabras clave
| Palabra | Qué significa | Ejemplo |
|---|---|---|
| Patrón de diseño | una solución conocida a un problema que se repite | |
| Estrategia | un algoritmo en una clase, que se puede cambiar | |
| Fábrica | una función que decide qué objeto crear | |
| Decorador | un objeto que envuelve a otro y le añade algo | |
| Interfaz | los métodos que varias clases ofrecen por igual | aplicar(importe) |
Las celdas que rellenan los estudiantes quedan en blanco en esta vista previa.
Un patrón es una receta, no código
Un patrón no se copia como una biblioteca. Es una forma de organizar clases que mucha gente ha comprobado. Se adapta a cada problema.
Se elige el patrón a partir del problema, y nunca al revés. La tabla une cada problema con su receta.
Ejemplo: del problema al patrón
| El problema | El patrón | Qué hace |
|---|---|---|
| Hay muchas maneras de calcular un descuento | Estrategia | Cada manera es una clase intercambiable |
| Hay que crear un objeto distinto según un código | Fábrica | Una función decide qué crear |
| Se pueden combinar extras en cualquier orden | Decorador | Cada extra envuelve al anterior |
Las celdas que rellenan los estudiantes quedan en blanco en esta vista previa.
La estrategia cambia el algoritmo del pedido
Cada descuento es una clase con el mismo método, aplicar(importe). Es la interfaz común. El pedido guarda un descuento y lo usa, sin saber cuál es.
Para cambiar la forma de calcular, se cambia el objeto. El pedido no se toca. Es composición, como en la lección anterior, con un objetivo concreto.
Tres descuentos intercambiables
# Cada descuento es una estrategia con el mismo método.
class SinDescuento:
def aplicar(self, importe):
return importe
class DescuentoFijo:
def __init__(self, cantidad):
self.cantidad = cantidad
def aplicar(self, importe):
return importe - self.cantidad
class DescuentoPorcentaje:
def __init__(self, porcentaje):
self.porcentaje = porcentaje
def aplicar(self, importe):
return importe - importe * self.porcentaje // 100
class Pedido:
def __init__(self, importe, descuento):
self.importe = importe
self.descuento = descuento
def total(self):
return self.descuento.aplicar(self.importe)
for descuento in [SinDescuento(), DescuentoFijo(5), DescuentoPorcentaje(10)]:
print(Pedido(30, descuento).total())
Prueba la estrategia en el laboratorio
Guarda Pedido y dos descuentos en pedido.py. Después cambia el descuento de un pedido ya creado y mira cómo cambia el total.
Los importes son euros enteros. Un pedido de 30 euros sin descuento cuesta 30.
Entra en la carpeta de trabajo
mkdir -p ~/oop && cd ~/oopSe muestra aquí y no se ejecuta. Crea una cuenta gratuita para pulsar Ejecutar y escribirlo en el terminal de tu propio laboratorio.
Escribe el pedido y dos descuentos
printf 'class SinDescuento:\n def aplicar(self, importe):\n return importe\n\n\nclass DescuentoFijo:\n def __init__(self, cantidad):\n self.cantidad = cantidad\n\n def aplicar(self, importe):\n return importe - self.cantidad\n\n\nclass Pedido:\n def __init__(self, importe, descuento):\n self.importe = importe\n self.descuento = descuento\n\n def total(self):\n return self.descuento.aplicar(self.importe)\n' > pedido.pySe muestra aquí y no se ejecuta. Crea una cuenta gratuita para pulsar Ejecutar y escribirlo en el terminal de tu propio laboratorio.
Cambia la estrategia de un pedido
python3 -c "from pedido import Pedido, SinDescuento, DescuentoFijo; p = Pedido(30, SinDescuento()); print(p.total()); p.descuento = DescuentoFijo(5); print(p.total())"Se muestra aquí y no se ejecuta. Crea una cuenta gratuita para pulsar Ejecutar y escribirlo en el terminal de tu propio laboratorio.
Un error frecuente
Advertencia
Mucha gente cree que un buen programa usa muchos patrones. No es así. Un patrón se usa cuando el problema aparece. Si solo hay un descuento y nunca habrá otro, una función sencilla es mejor que tres clases.
La fábrica decide qué objeto crear
El cupón llega como un texto, por ejemplo FIJO5. Alguien tiene que convertirlo en un objeto de descuento. Esa tarea es la fábrica.
Una fábrica es una función con la decisión. El resto del programa no repite los if. Si hay un cupón nuevo, se cambia solo la fábrica.
Ejemplo: qué crea la fábrica con cada cupón
| Cupón | Objeto creado | Total de un pedido de 30 € |
|---|---|---|
| FIJO5 | DescuentoFijo(5) | 25 € |
| cualquier otro texto | SinDescuento() | 30 € |
Las celdas que rellenan los estudiantes quedan en blanco en esta vista previa.
Escribe la fábrica de descuentos
printf 'from pedido import SinDescuento, DescuentoFijo\n\n\ndef crear_descuento(codigo):\n if codigo == "FIJO5":\n return DescuentoFijo(5)\n return SinDescuento()\n\n\nprint(crear_descuento("FIJO5").aplicar(30), crear_descuento("otro").aplicar(30))\n' > fabrica.pySe muestra aquí y no se ejecuta. Crea una cuenta gratuita para pulsar Ejecutar y escribirlo en el terminal de tu propio laboratorio.
Ejecuta la fábrica con dos cupones
python3 fabrica.pySe muestra aquí y no se ejecuta. Crea una cuenta gratuita para pulsar Ejecutar y escribirlo en el terminal de tu propio laboratorio.
El decorador envuelve un objeto
Un café cuesta 150 céntimos. Con leche suma 30 y con sirope suma 50. Si hubiera una clase por cada combinación, serían demasiadas.
El decorador es un objeto que guarda otro objeto y tiene su mismo método. Llama al método del objeto envuelto y le añade lo suyo. Se pueden envolver varias veces.
Cada extra envuelve a la bebida
# Cada adorno envuelve a una bebida y suma su precio, en céntimos.
class Cafe:
def coste(self):
return 150
class ConLeche:
def __init__(self, bebida):
self.bebida = bebida
def coste(self):
return self.bebida.coste() + 30
class ConSirope:
def __init__(self, bebida):
self.bebida = bebida
def coste(self):
return self.bebida.coste() + 50
print(Cafe().coste())
print(ConLeche(Cafe()).coste())
print(ConSirope(ConLeche(Cafe())).coste())
# Añade ConCanela, que suma 20 céntimos, y úsalo.
Dos cosas con el mismo nombre
Nota
El patrón decorador es un objeto que envuelve a otro. Es distinto de la sintaxis de Python que empieza con @ y que se usa sobre funciones. Aquí solo hablamos del patrón.