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…