Skip to article frontmatterSkip to article content
Site not loading correctly?

This may be due to an incorrect BASE_URL configuration. See the MyST Documentation for reference.

Patrones de Diseño Estructurales

Decorator

Agregar responsabilidades a objetos dinámicamente

Universidad Nacional de Rio Negro - Sede Andina

Definición

El patrón Decorator permite agregar responsabilidades a un objeto dinámicamente, proporcionando una alternativa flexible a la herencia para extender funcionalidad.

Origen e Historia

Gang of Four 1994. Proviene de la necesidad de evitar “explosión de subclases” cuando se combinan múltiples características. Popularizado en frameworks de I/O (Java streams: BufferedInputStream, DataInputStream, etc).

Motivación

Necesario cuando:

Contexto

Patrón: Componente base → Stack de decoradores

Anatomía:

Cuando aplica

Usá Decorator cuando:

Cuando no aplica

Evitá Decorator cuando:

Consecuencias de su uso

Positivas

Negativas

Alternativas

AspectoDecoratorProxyStrategy
IntenciónAgregar responsabilidadesControlar accesoEncapsular algoritmo
ComposiciónMúltipleUno-a-unoIntercambiable
TimingTiempo de objetoTiempo de objetoTiempo de uso

Estructura

Problema

// ❌ Explosión de subclases
abstract class Bebida {
    abstract double costo();
}

class Café extends Bebida {
    public double costo() { return 3.00; }
}

class CaféConLeche extends Café {
    public double costo() { return 3.00 + 0.50; }
}

class CaféConLecheYAzúcar extends CaféConLeche {
    public double costo() { return 3.00 + 0.50 + 0.10; }
}

// Cada combinación es una clase nueva!

Solución

/**
 * Componente base.
 */
public abstract class Bebida {
    protected String descripcion = "Bebida desconocida";
    
    public String getDescripcion() {
        return descripcion;
    }
    
    abstract double costo();
}

/**
 * Componente concreto: implementación simple.
 */
public class Café extends Bebida {
    public Café() {
        descripcion = "Café";
    }
    
    @Override
    public double costo() {
        return 3.00;
    }
}

/**
 * Clase Decorator base: también es Bebida.
 */
public abstract class AditamentoBebida extends Bebida {
    protected Bebida bebidaDecorada;
    
    public AditamentoBebida(Bebida bebida) {
        this.bebidaDecorada = bebida;
    }
    
    @Override
    public String getDescripcion() {
        return bebidaDecorada.getDescripcion();
    }
}

/**
 * Decorador concreto: Leche.
 */
public class Leche extends AditamentoBebida {
    public Leche(Bebida bebida) {
        super(bebida);
    }
    
    @Override
    public String getDescripcion() {
        return bebidaDecorada.getDescripcion() + ", leche";
    }
    
    @Override
    public double costo() {
        return bebidaDecorada.costo() + 0.50;
    }
}

/**
 * Decorador concreto: Azúcar.
 */
public class Azúcar extends AditamentoBebida {
    public Azúcar(Bebida bebida) {
        super(bebida);
    }
    
    @Override
    public String getDescripcion() {
        return bebidaDecorada.getDescripcion() + ", azúcar";
    }
    
    @Override
    public double costo() {
        return bebidaDecorada.costo() + 0.10;
    }
}

// ✅ Composición flexible
Bebida café = new Café();                              // 3.00
Bebida caféConLeche = new Leche(café);                 // 3.50
Bebida caféConLecheYAzúcar = new Azúcar(caféConLeche); // 3.60

System.out.println(caféConLecheYAzúcar.getDescripcion()); // Café, leche, azúcar
System.out.println("Costo: $" + caféConLecheYAzúcar.costo()); // 3.60

Diagramas

Diagrama de Clases

Diagrama de Secuencia

Ejemplos

Ejemplo 1: Streams de Entrada

public interface InputStream {
    int read();
    void close();
}

public class FileInputStream implements InputStream {
    @Override
    public int read() {
        System.out.println("Leyendo desde archivo");
        return 42;
    }
    
    @Override
    public void close() {
        System.out.println("Archivo cerrado");
    }
}

public abstract class StreamDecorator implements InputStream {
    protected InputStream stream;
    
    public StreamDecorator(InputStream stream) {
        this.stream = stream;
    }
}

public class BufferedStream extends StreamDecorator {
    private byte[] buffer = new byte[1024];
    
    public BufferedStream(InputStream stream) {
        super(stream);
    }
    
    @Override
    public int read() {
        System.out.println("[Buffered] Leyendo con buffer");
        return stream.read();
    }
    
    @Override
    public void close() {
        stream.close();
    }
}

public class CompressedStream extends StreamDecorator {
    public CompressedStream(InputStream stream) {
        super(stream);
    }
    
    @Override
    public int read() {
        System.out.println("[Comprimido] Descomprimiendo");
        return stream.read();
    }
    
    @Override
    public void close() {
        stream.close();
    }
}

// Uso
InputStream file = new FileInputStream();
InputStream buffered = new BufferedStream(file);
InputStream final_stream = new CompressedStream(buffered);

final_stream.read();  // Descomprimiendo -> Buffered -> archivo

Ejercicios

Resumen

El patrón Decorator es esencial para agregar comportamiento dinámicamente sin crear explosión de subclases. Su flexibilidad permite combinar múltiples características en tiempo de ejecución. Aunque introduce complejidad, su adherencia al principio Open/Closed y Single Responsibility lo hacen invaluable en arquitectura extensible.

Próximo paso

Para seguir, conviene pasar a el material siguiente, donde el recorrido continúa sobre esta base.