Bridge (Brygga)

Introduktion

Bridge är ett strukturmönster som delar upp en stor klass — eller en familj av nära relaterade klasser — i två separata hierarkier: abstraktion och implementation. De kan sedan utvecklas oberoende av varandra.


Problem

Du har en Shape-klass med underklasserna Circle och Square. Du vill lägga till färger — Red och Blue. Med arv får du RedCircle, BlueCircle, RedSquare, BlueSquare — fyra klasser för två former och två färger. Lägger du till en tredje färg behöver du ytterligare två klasser. Kombinationerna exploderar.

Lösning

Bridge extraherar en av dimensionerna till en separat hierarki. Formerna håller en referens till ett färgobjekt istället för att ärva det. Form och färg kan nu utökas helt oberoende.

När ska Bridge användas?

  • När du vill undvika en permanent bindning mellan abstraktion och implementation.
  • När både abstraktioner och implementationer ska kunna utökas via arv oberoende.
  • När ändringar i implementationen inte ska påverka klientkoden.

Struktur

RollAnsvar
AbstractionHåller en referens till Implementation; definierar det högnivågränssnitt klienten använder.
Refined AbstractionUtökar Abstraction med extra logik.
Implementation InterfaceDefinierar gränssnittet för implementationsklasserna.
Concrete ImplementationSpecifik implementation av Implementation-gränssnittet.

Exempel — Enhetsnotifieringar (Java)

Scenariot: Notifieringar (BasicNotification, UrgentNotification) ska kunna skickas via olika kanaler (EmailSender, SmsSender) — helt oberoende av varandra.

// ── Implementation Interface ──────────────────────────────

public interface MessageSender {
    void sendMessage(String recipient, String subject, String body);
}

// ── Concrete Implementations ──────────────────────────────

public class EmailSender implements MessageSender {
    @Override
    public void sendMessage(String recipient, String subject, String body) {
        System.out.println("📧 E-post till " + recipient);
        System.out.println("   Ämne: " + subject);
        System.out.println("   Meddelande: " + body);
    }
}

public class SmsSender implements MessageSender {
    @Override
    public void sendMessage(String recipient, String subject, String body) {
        System.out.println("📱 SMS till " + recipient + ": " + subject + " — " + body);
    }
}

public class SlackSender implements MessageSender {
    private String workspace;

    public SlackSender(String workspace) {
        this.workspace = workspace;
    }

    @Override
    public void sendMessage(String recipient, String subject, String body) {
        System.out.println("💬 Slack [" + workspace + "] → " + recipient + ": " + subject);
    }
}

// ── Abstraction ───────────────────────────────────────────

public abstract class Notification {
    protected MessageSender sender;

    public Notification(MessageSender sender) {
        this.sender = sender;
    }

    public abstract void notify(String recipient, String event);
}

// ── Refined Abstractions ──────────────────────────────────

public class BasicNotification extends Notification {
    public BasicNotification(MessageSender sender) {
        super(sender);
    }

    @Override
    public void notify(String recipient, String event) {
        sender.sendMessage(recipient, "Notifiering", "Händelse inträffade: " + event);
    }
}

public class UrgentNotification extends Notification {
    public UrgentNotification(MessageSender sender) {
        super(sender);
    }

    @Override
    public void notify(String recipient, String event) {
        sender.sendMessage(
            recipient,
            "🚨 BRÅDSKANDE: " + event,
            "Kräver omedelbar åtgärd! Händelse: " + event
        );
    }
}

public class DigestNotification extends Notification {
    private java.util.List<String> events = new java.util.ArrayList<>();

    public DigestNotification(MessageSender sender) {
        super(sender);
    }

    public void addEvent(String event) {
        events.add(event);
    }

    @Override
    public void notify(String recipient, String title) {
        String body = String.join(", ", events);
        sender.sendMessage(recipient, "Sammanfattning: " + title, body);
        events.clear();
    }
}

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

public class Application {
    public static void main(String[] args) {
        // Kombinera abstraktioner med implementationer fritt
        Notification emailAlert = new UrgentNotification(new EmailSender());
        emailAlert.notify("ops@bytebase.se", "Databasen svarar inte");

        System.out.println();

        Notification smsBasic = new BasicNotification(new SmsSender());
        smsBasic.notify("+46701234567", "Schemalagt jobb slutfört");

        System.out.println();

        DigestNotification digest = new DigestNotification(new SlackSender("bytebase"));
        digest.addEvent("Användare registrerad");
        digest.addEvent("Order skapad");
        digest.addEvent("Betalning mottagen");
        digest.notify("#daglig-rapport", "Dagens händelser");
    }
}

Output:

📧 E-post till ops@bytebase.se
   Ämne: 🚨 BRÅDSKANDE: Databasen svarar inte
   Meddelande: Kräver omedelbar åtgärd! Händelse: Databasen svarar inte

📱 SMS till +46701234567: Notifiering — Händelse inträffade: Schemalagt jobb slutfört

💬 Slack [bytebase] → #daglig-rapport: Sammanfattning: Dagens händelser

Fördelar

  • Open/Closed Principle — Lägg till nya abstraktioner och implementationer oberoende av varandra.
  • Undviker kombinatorisk explosion av underklasser.
  • Klientkoden arbetar mot abstraktionen och behöver aldrig känna till implementationen.

Nackdelar

  • Ökar komplexiteten — ytterligare ett lager av inriktning kan göra enkel kod svårare att följa.

Av Victor Hernandez från Bytebase.se