¿Te resulta más fácil leer con fondo crema, un tono de color, más contraste o un texto más grande? Elige tus colores y tu texto una sola vez, para todas las páginas.

Lección gratuita

Patrones: fábrica, estrategia y decorador

Después de esta lección: Puedo aplicar una fábrica, una estrategia y un decorador.

Lección de 80 min5 comandosLaboratorio de comandos de Linux de cada día: los treinta comandos que se escriben de memoria

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

PalabraQué significaEjemplo
Patrón de diseñouna solución conocida a un problema que se repite
Estrategiaun algoritmo en una clase, que se puede cambiar
Fábricauna función que decide qué objeto crear
Decoradorun objeto que envuelve a otro y le añade algo
Interfazlos métodos que varias clases ofrecen por igualaplicar(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 problemaEl patrónQué hace
Hay muchas maneras de calcular un descuentoEstrategiaCada manera es una clase intercambiable
Hay que crear un objeto distinto según un códigoFábricaUna función decide qué crear
Se pueden combinar extras en cualquier ordenDecoradorCada 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

Código: python
# 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

Comando
mkdir -p ~/oop && cd ~/oop

Se 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

Comando
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.py

Se 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

Comando
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ónObjeto creadoTotal de un pedido de 30 €
FIJO5DescuentoFijo(5)25 €
cualquier otro textoSinDescuento()30 €

Las celdas que rellenan los estudiantes quedan en blanco en esta vista previa.

Escribe la fábrica de descuentos

Comando
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.py

Se 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

Comando
python3 fabrica.py

Se 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

Código: python
# 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.

Sobre este laboratorio y su código: Laboratorio de comandos de Linux de cada día: los treinta comandos que se escriben de memoria

¿Enseñas a una clase? Asigna este módulo a tus alumnos con guía del profesor, tareas con corrección automática y un laboratorio para cada alumno.