iwantcoding.com
🔥 Daily 👥 Rooms 🏆 Top Log in Sign up

Bridge

Bridge decouples an abstraction from its implementation so both can vary independently. The canonical example: a Notification abstraction (Sms, Email, Push) crossed with a Sender implementation (Twilio, SendGrid, FCM). Without Bridge you write 3x3 classes; with Bridge you write 3+3 and compose them at runtime.

Notification × Sender with Bridge

EXAMPLE
<?php
// ============================================================
// Implementation side: how a message physically gets sent
// ============================================================
interface Sender {
    public function send(string $to, string $subject, string $body): void;
}

class TwilioSender implements Sender {
    public function send(string $to, string $subject, string $body): void {
        echo "[twilio] {$to}: {$subject} | {$body}\n";
    }
}

class SendGridSender implements Sender {
    public function send(string $to, string $subject, string $body): void {
        echo "[sendgrid] {$to}: {$subject}\n{$body}\n";
    }
}

class FcmSender implements Sender {
    public function send(string $to, string $subject, string $body): void {
        echo "[fcm] device {$to}: {$subject} :: {$body}\n";
    }
}

// ============================================================
// Abstraction side: WHAT to send, given some payload shape
// ============================================================
abstract class Notification {
    public function __construct(protected Sender $sender) {}
    abstract public function notify(string $to, array $payload): void;
}

class OrderShippedSms extends Notification {
    public function notify(string $to, array $p): void {
        $this->sender->send($to,
            'Order shipped',
            "Order #{$p['order']} is on its way. Tracking: {$p['tracking']}");
    }
}

class OrderShippedEmail extends Notification {
    public function notify(string $to, array $p): void {
        $body = "Hi {$p['name']},\n\nYour order #{$p['order']} shipped today.\n"
              . "Tracking number: {$p['tracking']}\n\nThanks!";
        $this->sender->send($to, "Your order #{$p['order']} has shipped", $body);
    }
}

class PriceDropPush extends Notification {
    public function notify(string $to, array $p): void {
        $this->sender->send($to,
            'Price drop!',
            "{$p['product']} is now \${$p['price']}");
    }
}

// ============================================================
// Compose at runtime — any notification × any transport
// ============================================================
$twilio = new TwilioSender();
$mail   = new SendGridSender();
$push   = new FcmSender();

(new OrderShippedSms($twilio))->notify('+61412000111',
    ['order' => 7001, 'tracking' => 'AU-9F3']);

(new OrderShippedEmail($mail))->notify('alice@example.com',
    ['name' => 'Alice', 'order' => 7001, 'tracking' => 'AU-9F3']);

(new PriceDropPush($push))->notify('device-uuid-x',
    ['product' => 'Wool coat', 'price' => 199]);

Why it matters

Bridge is the right call once a hierarchy crosses two dimensions: kind of thing × how it is delivered. If you only have one dimension, Strategy is simpler and reads better. The smell that triggers Bridge is class names like EmailSmsSender, EmailFcmSender — the cartesian product is already starting.

Tip: Tweak the snippet with Try it Yourself », then sit the quiz at the bottom of the page.

Example

Example
// Decouple abstraction from implementation.
class NotificationSender {
    constructor(channel) { this.channel = channel; }
    send(msg) { return this.channel.deliver(msg); }
}
new NotificationSender(new EmailChannel()).send('hi');
Try it Yourself »

Discussion

Loading…