Introducció al Patró Decorator

El patró Decorator és un patró estructural que permet afegir funcionalitats addicionals a un objecte de manera dinàmica. Aquest patró és especialment útil quan es volen afegir responsabilitats a objectes individuals de manera flexible i sense afectar altres objectes de la mateixa classe.

Objectius del Patró Decorator

  • Flexibilitat: Permet afegir funcionalitats a objectes de manera dinàmica.
  • Reutilització: Facilita la reutilització de codi, ja que les funcionalitats addicionals es poden encapsular en decoradors reutilitzables.
  • Simplicitat: Evita la creació de subclasses per cada combinació possible de funcionalitats.

Components del Patró Decorator

  1. Component: Defineix la interfície per als objectes que poden tenir responsabilitats afegides dinàmicament.
  2. ConcreteComponent: Implementa la interfície Component i representa l'objecte original al qual es poden afegir responsabilitats.
  3. Decorator: Manté una referència a un objecte Component i defineix una interfície que segueix la interfície del Component.
  4. ConcreteDecorator: Afegeix responsabilitats addicionals al Component.

Diagrama UML del Patró Decorator

    +-------------------+
    |     Component     |
    +-------------------+
    | +operation():void |
    +-------------------+
            ^
            |
    +-------------------+
    | ConcreteComponent |
    +-------------------+
    | +operation():void |
    +-------------------+
            ^
            |
    +-------------------+
    |     Decorator     |
    +-------------------+
    | -component:Component |
    | +operation():void |
    +-------------------+
            ^
            |
    +-------------------+
    | ConcreteDecorator |
    +-------------------+
    | +operation():void |
    +-------------------+

Exemple Pràctic

Escenari

Suposem que estem desenvolupant una aplicació de cafè on els clients poden personalitzar les seves begudes amb diferents addicions com llet, sucre, etc. Utilitzarem el patró Decorator per afegir aquestes funcionalitats de manera dinàmica.

Implementació en Java

Component

// Component
public interface Beverage {
    String getDescription();
    double cost();
}

ConcreteComponent

// ConcreteComponent
public class Espresso implements Beverage {
    @Override
    public String getDescription() {
        return "Espresso";
    }

    @Override
    public double cost() {
        return 1.99;
    }
}

Decorator

// Decorator
public abstract class CondimentDecorator implements Beverage {
    protected Beverage beverage;

    public CondimentDecorator(Beverage beverage) {
        this.beverage = beverage;
    }

    @Override
    public String getDescription() {
        return beverage.getDescription();
    }

    @Override
    public double cost() {
        return beverage.cost();
    }
}

ConcreteDecorator

// ConcreteDecorator
public class Milk extends CondimentDecorator {
    public Milk(Beverage beverage) {
        super(beverage);
    }

    @Override
    public String getDescription() {
        return beverage.getDescription() + ", Milk";
    }

    @Override
    public double cost() {
        return beverage.cost() + 0.50;
    }
}

public class Sugar extends CondimentDecorator {
    public Sugar(Beverage beverage) {
        super(beverage);
    }

    @Override
    public String getDescription() {
        return beverage.getDescription() + ", Sugar";
    }

    @Override
    public double cost() {
        return beverage.cost() + 0.20;
    }
}

Client

public class CoffeeShop {
    public static void main(String[] args) {
        Beverage beverage = new Espresso();
        System.out.println(beverage.getDescription() + " $" + beverage.cost());

        beverage = new Milk(beverage);
        System.out.println(beverage.getDescription() + " $" + beverage.cost());

        beverage = new Sugar(beverage);
        System.out.println(beverage.getDescription() + " $" + beverage.cost());
    }
}

Sortida Esperada

Espresso $1.99
Espresso, Milk $2.49
Espresso, Milk, Sugar $2.69

Exercicis Pràctics

Exercici 1

Implementa un nou decorador anomenat Mocha que afegeixi la descripció "Mocha" i un cost addicional de $0.75 a una beguda.

Solució

public class Mocha extends CondimentDecorator {
    public Mocha(Beverage beverage) {
        super(beverage);
    }

    @Override
    public String getDescription() {
        return beverage.getDescription() + ", Mocha";
    }

    @Override
    public double cost() {
        return beverage.cost() + 0.75;
    }
}

Exercici 2

Crea una nova classe Latte que sigui un ConcreteComponent i tingui un cost de $2.50.

Solució

public class Latte implements Beverage {
    @Override
    public String getDescription() {
        return "Latte";
    }

    @Override
    public double cost() {
        return 2.50;
    }
}

Errors Comuns i Consells

  • No oblidar cridar al mètode del component original: Quan s'implementa un decorador, és important assegurar-se que es crida al mètode del component original per mantenir la funcionalitat existent.
  • Evitar la creació de subclasses innecessàries: El patró Decorator permet afegir funcionalitats sense necessitat de crear subclasses per cada combinació possible de funcionalitats.

Conclusió

El patró Decorator és una eina poderosa per afegir funcionalitats a objectes de manera dinàmica i flexible. En lloc de crear subclasses per cada combinació de funcionalitats, podem utilitzar decoradors per afegir responsabilitats de manera modular i reutilitzable. Aquest patró és especialment útil en aplicacions on les funcionalitats poden variar de manera dinàmica i es necessiten solucions flexibles i escalables.

© Copyright 2024. Tots els drets reservats