Decorator (Dekoratör)

Introduktion

Decorator är ett strukturmönster som låter dig lägga till nytt beteende till ett objekt genom att linda in det i ett dekoratörobjekt. Det är ett flexibelt alternativ till arv för att utöka funktionalitet.


Problem

Du bygger ett notifieringssystem. Från början skickar det bara e-post. Snart vill användare också ha SMS-notifieringar. Sedan Slack. Sedan pushnotifieringar.

Du kan inte skapa en underklass för varje kombination — det skulle bli EmailAndSms, EmailAndSlack, EmailAndSmsAndSlack… En kombinatorisk explosion. Arv är fel verktyg här.

Lösning

Decorator lindar in ett objekt och lägger till beteende runt det befintliga. Dekoratörer implementerar samma gränssnitt som det objekt de lindar in och kan staplas hur som helst vid runtime.

När ska Decorator användas?

  • När du vill lägga till beteende till enskilda objekt utan att påverka andra instanser av samma klass.
  • När arv är opraktiskt på grund av för många kombinationer.
  • När du vill kunna kombinera beteenden fritt vid runtime.

Struktur

RollAnsvar
Component InterfaceDefinierar gränssnittet för både wrappade objekt och dekoratörer.
Concrete ComponentDet ursprungliga objektet med grundläggande beteende.
Base DecoratorHåller en referens till det wrappade objektet och delegerar till det.
Concrete DecoratorLägger till extra beteende före/efter delegationen.

Exempel — Notifieringssystem (Java)

Scenariot: En Notifier kan utökas med e-post, SMS och Slack-notifieringar — i valfri kombination — utan att ändra grundklassen.

// ── Component Interface ───────────────────────────────────

public interface Notifier {
    void send(String message);
}

// ── Concrete Component ────────────────────────────────────

public class BaseNotifier implements Notifier {
    private String recipient;

    public BaseNotifier(String recipient) {
        this.recipient = recipient;
    }

    @Override
    public void send(String message) {
        System.out.println("📧 E-post till " + recipient + ": " + message);
    }
}

// ── Base Decorator ────────────────────────────────────────

public abstract class NotifierDecorator implements Notifier {
    protected Notifier wrapped;

    public NotifierDecorator(Notifier wrapped) {
        this.wrapped = wrapped;
    }

    @Override
    public void send(String message) {
        wrapped.send(message);
    }
}

// ── Concrete Decorators ───────────────────────────────────

public class SmsDecorator extends NotifierDecorator {
    private String phoneNumber;

    public SmsDecorator(Notifier wrapped, String phoneNumber) {
        super(wrapped);
        this.phoneNumber = phoneNumber;
    }

    @Override
    public void send(String message) {
        super.send(message);
        System.out.println("📱 SMS till " + phoneNumber + ": " + message);
    }
}

public class SlackDecorator extends NotifierDecorator {
    private String channel;

    public SlackDecorator(Notifier wrapped, String channel) {
        super(wrapped);
        this.channel = channel;
    }

    @Override
    public void send(String message) {
        super.send(message);
        System.out.println("💬 Slack #" + channel + ": " + message);
    }
}

public class PushDecorator extends NotifierDecorator {
    private String deviceToken;

    public PushDecorator(Notifier wrapped, String deviceToken) {
        super(wrapped);
        this.deviceToken = deviceToken;
    }

    @Override
    public void send(String message) {
        super.send(message);
        System.out.println("🔔 Push till enhet " + deviceToken + ": " + message);
    }
}

// ── Klientkod ─────────────────────────────────────────────

public class Application {
    public static void main(String[] args) {
        // Grundläggande e-postnotifiering
        Notifier notifier = new BaseNotifier("anna@bytebase.se");

        // Lägg till SMS
        notifier = new SmsDecorator(notifier, "+46701234567");

        // Lägg till Slack
        notifier = new SlackDecorator(notifier, "ops-alerts");

        // Lägg till pushnotifiering
        notifier = new PushDecorator(notifier, "device-abc123");

        // Ett anrop — alla kanaler notifieras
        notifier.send("Servern är nere!");
    }
}

Output:

📧 E-post till anna@bytebase.se: Servern är nere!
📱 SMS till +46701234567: Servern är nere!
💬 Slack #ops-alerts: Servern är nere!
🔔 Push till enhet device-abc123: Servern är nere!

Fördelar

  • Single Responsibility Principle — Varje dekoratör hanterar ett enda extra beteende.
  • Open/Closed Principle — Lägg till nya dekoratörer utan att ändra befintlig kod.
  • Kombinera beteenden fritt vid runtime — ingen kombinatorisk explosion av underklasser.

Nackdelar

  • En stack av många dekoratörer kan vara svår att felsöka.
  • Ordningen på dekoratörerna spelar roll och kan ge oväntade resultat.

Av Victor Hernandez från Bytebase.se