Laravel 13 桥接模式深度解析
桥接模式是一种结构型设计模式,它将抽象部分与实现部分分离,使它们可以独立变化。本文将深入探讨 Laravel 13 中桥接模式的高级用法。
桥接模式基础
什么是桥接模式
桥接模式通过将继承关系转换为组合关系,实现了抽象和实现的解耦。
1 2 3 4 5 6 7 8
| <?php
namespace App\Contracts;
interface ImplementationInterface { public function operationImplementation(): string; }
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15
| <?php
namespace App\Abstractions;
abstract class Abstraction { protected ImplementationInterface $implementation;
public function __construct(ImplementationInterface $implementation) { $this->implementation = $implementation; }
abstract public function operation(): string; }
|
1 2 3 4 5 6 7 8 9 10 11 12 13
| <?php
namespace App\RefinedAbstractions;
use App\Abstractions\Abstraction;
class RefinedAbstraction extends Abstraction { public function operation(): string { return "Refined: " . $this->implementation->operationImplementation(); } }
|
消息通知桥接
消息发送器接口
1 2 3 4 5 6 7 8 9 10 11 12
| <?php
namespace App\Contracts\Messaging;
interface MessageSenderInterface { public function send(string $to, string $subject, string $body): bool;
public function sendBatch(array $recipients, string $subject, string $body): array;
public function getProviderName(): string; }
|
具体发送器实现
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38
| <?php
namespace App\Messaging\Senders;
use App\Contracts\Messaging\MessageSenderInterface; use Illuminate\Support\Facades\Mail;
class EmailSender implements MessageSenderInterface { public function send(string $to, string $subject, string $body): bool { try { Mail::raw($body, function ($message) use ($to, $subject) { $message->to($to)->subject($subject); });
return true; } catch (\Exception $e) { return false; } }
public function sendBatch(array $recipients, string $subject, string $body): array { $results = [];
foreach ($recipients as $recipient) { $results[$recipient] = $this->send($recipient, $subject, $body); }
return $results; }
public function getProviderName(): string { return 'email'; } }
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48
| <?php
namespace App\Messaging\Senders;
use App\Contracts\Messaging\MessageSenderInterface; use Twilio\Rest\Client;
class SmsSender implements MessageSenderInterface { protected Client $client; protected string $fromNumber;
public function __construct(array $config) { $this->client = new Client($config['sid'], $config['token']); $this->fromNumber = $config['from']; }
public function send(string $to, string $subject, string $body): bool { try { $this->client->messages->create($to, [ 'from' => $this->fromNumber, 'body' => $body, ]);
return true; } catch (\Exception $e) { return false; } }
public function sendBatch(array $recipients, string $subject, string $body): array { $results = [];
foreach ($recipients as $recipient) { $results[$recipient] = $this->send($recipient, $subject, $body); }
return $results; }
public function getProviderName(): string { return 'sms'; } }
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44
| <?php
namespace App\Messaging\Senders;
use App\Contracts\Messaging\MessageSenderInterface;
class PushSender implements MessageSenderInterface { public function send(string $to, string $subject, string $body): bool { try { $notification = [ 'to' => $to, 'notification' => [ 'title' => $subject, 'body' => $body, ], ];
$response = Http::withToken(config('services.fcm.token')) ->post('https://fcm.googleapis.com/fcm/send', $notification);
return $response->successful(); } catch (\Exception $e) { return false; } }
public function sendBatch(array $recipients, string $subject, string $body): array { $results = [];
foreach ($recipients as $recipient) { $results[$recipient] = $this->send($recipient, $subject, $body); }
return $results; }
public function getProviderName(): string { return 'push'; } }
|
消息抽象层
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33
| <?php
namespace App\Messaging\Messages;
use App\Contracts\Messaging\MessageSenderInterface;
abstract class Message { protected MessageSenderInterface $sender; protected string $subject; protected string $body;
public function __construct(MessageSenderInterface $sender) { $this->sender = $sender; }
public function setSubject(string $subject): self { $this->subject = $subject; return $this; }
public function setBody(string $body): self { $this->body = $body; return $this; }
abstract public function send(string $to): bool;
abstract public function formatBody(): string; }
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26
| <?php
namespace App\Messaging\Messages;
class WelcomeMessage extends Message { protected string $userName;
public function setUserName(string $name): self { $this->userName = $name; return $this; }
public function send(string $to): bool { $formattedBody = $this->formatBody();
return $this->sender->send($to, $this->subject, $formattedBody); }
public function formatBody(): string { return str_replace('{name}', $this->userName, $this->body); } }
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37
| <?php
namespace App\Messaging\Messages;
class OrderConfirmationMessage extends Message { protected string $orderNumber; protected float $total;
public function setOrderNumber(string $orderNumber): self { $this->orderNumber = $orderNumber; return $this; }
public function setTotal(float $total): self { $this->total = $total; return $this; }
public function send(string $to): bool { $formattedBody = $this->formatBody();
return $this->sender->send($to, $this->subject, $formattedBody); }
public function formatBody(): string { return str_replace( ['{order_number}', '{total}'], [$this->orderNumber, number_format($this->total, 2)], $this->body ); } }
|
数据库驱动桥接
数据库驱动接口
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22
| <?php
namespace App\Contracts\Database;
interface DriverInterface { public function connect(array $config): void;
public function disconnect(): void;
public function query(string $sql, array $bindings = []): array;
public function execute(string $sql, array $bindings = []): int;
public function beginTransaction(): void;
public function commit(): void;
public function rollback(): void;
public function getLastInsertId(): string; }
|
具体驱动实现
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66
| <?php
namespace App\Database\Drivers;
use App\Contracts\Database\DriverInterface; use PDO;
class MySqlDriver implements DriverInterface { protected ?PDO $pdo = null;
public function connect(array $config): void { $dsn = sprintf( 'mysql:host=%s;port=%s;dbname=%s;charset=%s', $config['host'], $config['port'] ?? 3306, $config['database'], $config['charset'] ?? 'utf8mb4' );
$this->pdo = new PDO($dsn, $config['username'], $config['password'], [ PDO::ATTR_ERRMODE => PDO::ERRMODE_EXCEPTION, PDO::ATTR_DEFAULT_FETCH_MODE => PDO::FETCH_ASSOC, ]); }
public function disconnect(): void { $this->pdo = null; }
public function query(string $sql, array $bindings = []): array { $stmt = $this->pdo->prepare($sql); $stmt->execute($bindings); return $stmt->fetchAll(); }
public function execute(string $sql, array $bindings = []): int { $stmt = $this->pdo->prepare($sql); $stmt->execute($bindings); return $stmt->rowCount(); }
public function beginTransaction(): void { $this->pdo->beginTransaction(); }
public function commit(): void { $this->pdo->commit(); }
public function rollback(): void { $this->pdo->rollBack(); }
public function getLastInsertId(): string { return $this->pdo->lastInsertId(); } }
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65
| <?php
namespace App\Database\Drivers;
use App\Contracts\Database\DriverInterface; use PDO;
class PostgresDriver implements DriverInterface { protected ?PDO $pdo = null;
public function connect(array $config): void { $dsn = sprintf( 'pgsql:host=%s;port=%s;dbname=%s', $config['host'], $config['port'] ?? 5432, $config['database'] );
$this->pdo = new PDO($dsn, $config['username'], $config['password'], [ PDO::ATTR_ERRMODE => PDO::ERRMODE_EXCEPTION, PDO::ATTR_DEFAULT_FETCH_MODE => PDO::FETCH_ASSOC, ]); }
public function disconnect(): void { $this->pdo = null; }
public function query(string $sql, array $bindings = []): array { $stmt = $this->pdo->prepare($sql); $stmt->execute($bindings); return $stmt->fetchAll(); }
public function execute(string $sql, array $bindings = []): int { $stmt = $this->pdo->prepare($sql); $stmt->execute($bindings); return $stmt->rowCount(); }
public function beginTransaction(): void { $this->pdo->beginTransaction(); }
public function commit(): void { $this->pdo->commit(); }
public function rollback(): void { $this->pdo->rollBack(); }
public function getLastInsertId(): string { return $this->pdo->lastInsertId(); } }
|
查询构建器抽象
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44
| <?php
namespace App\Database\Query;
use App\Contracts\Database\DriverInterface;
abstract class QueryBuilder { protected DriverInterface $driver; protected string $table; protected array $wheres = []; protected array $columns = ['*']; protected array $bindings = [];
public function __construct(DriverInterface $driver) { $this->driver = $driver; }
public function table(string $table): self { $this->table = $table; return $this; }
public function select(array $columns = ['*']): self { $this->columns = $columns; return $this; }
public function where(string $column, string $operator, mixed $value): self { $this->wheres[] = compact('column', 'operator', 'value'); $this->bindings[] = $value; return $this; }
abstract public function get(): array;
abstract public function first(): ?array;
abstract protected function compileSelect(): string; }
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34
| <?php
namespace App\Database\Query;
class MySqlQueryBuilder extends QueryBuilder { public function get(): array { $sql = $this->compileSelect(); return $this->driver->query($sql, $this->bindings); }
public function first(): ?array { $results = $this->get(); return $results[0] ?? null; }
protected function compileSelect(): string { $columns = implode(', ', $this->columns); $sql = "SELECT {$columns} FROM {$this->table}";
if (!empty($this->wheres)) { $clauses = array_map( fn($where) => "{$where['column']} {$where['operator']} ?", $this->wheres ); $sql .= ' WHERE ' . implode(' AND ', $clauses); }
return $sql; } }
|
渲染器桥接
渲染器接口
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18
| <?php
namespace App\Contracts\Rendering;
interface RendererInterface { public function renderTitle(string $title): string;
public function renderText(string $text): string;
public function renderLink(string $text, string $url): string;
public function renderImage(string $src, string $alt): string;
public function renderList(array $items): string;
public function renderTable(array $headers, array $rows): string; }
|
具体渲染器实现
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47
| <?php
namespace App\Rendering\Renderers;
use App\Contracts\Rendering\RendererInterface;
class HtmlRenderer implements RendererInterface { public function renderTitle(string $title): string { return "<h1>{$title}</h1>"; }
public function renderText(string $text): string { return "<p>{$text}</p>"; }
public function renderLink(string $text, string $url): string { return "<a href=\"{$url}\">{$text}</a>"; }
public function renderImage(string $src, string $alt): string { return "<img src=\"{$src}\" alt=\"{$alt}\">"; }
public function renderList(array $items): string { $listItems = array_map(fn($item) => "<li>{$item}</li>", $items); return "<ul>" . implode('', $listItems) . "</ul>"; }
public function renderTable(array $headers, array $rows): string { $headerCells = array_map(fn($h) => "<th>{$h}</th>", $headers); $headerRow = "<tr>" . implode('', $headerCells) . "</tr>";
$bodyRows = array_map(function ($row) { $cells = array_map(fn($cell) => "<td>{$cell}</td>", $row); return "<tr>" . implode('', $cells) . "</tr>"; }, $rows);
return "<table><thead>{$headerRow}</thead><tbody>" . implode('', $bodyRows) . "</tbody></table>"; } }
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46
| <?php
namespace App\Rendering\Renderers;
use App\Contracts\Rendering\RendererInterface;
class MarkdownRenderer implements RendererInterface { public function renderTitle(string $title): string { return "# {$title}\n\n"; }
public function renderText(string $text): string { return "{$text}\n\n"; }
public function renderLink(string $text, string $url): string { return "[{$text}]({$url})"; }
public function renderImage(string $src, string $alt): string { return ""; }
public function renderList(array $items): string { $listItems = array_map(fn($item) => "- {$item}", $items); return implode("\n", $listItems) . "\n\n"; }
public function renderTable(array $headers, array $rows): string { $headerLine = "| " . implode(" | ", $headers) . " |"; $separatorLine = "| " . implode(" | ", array_fill(0, count($headers), "---")) . " |";
$bodyLines = array_map(function ($row) { return "| " . implode(" | ", $row) . " |"; }, $rows);
return $headerLine . "\n" . $separatorLine . "\n" . implode("\n", $bodyLines) . "\n\n"; } }
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41
| <?php
namespace App\Rendering\Renderers;
use App\Contracts\Rendering\RendererInterface;
class JsonRenderer implements RendererInterface { protected array $data = [];
public function renderTitle(string $title): string { $this->data['title'] = $title; return json_encode(['type' => 'title', 'content' => $title]); }
public function renderText(string $text): string { return json_encode(['type' => 'text', 'content' => $text]); }
public function renderLink(string $text, string $url): string { return json_encode(['type' => 'link', 'text' => $text, 'url' => $url]); }
public function renderImage(string $src, string $alt): string { return json_encode(['type' => 'image', 'src' => $src, 'alt' => $alt]); }
public function renderList(array $items): string { return json_encode(['type' => 'list', 'items' => $items]); }
public function renderTable(array $headers, array $rows): string { return json_encode(['type' => 'table', 'headers' => $headers, 'rows' => $rows]); } }
|
页面抽象
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17
| <?php
namespace App\Rendering\Pages;
use App\Contracts\Rendering\RendererInterface;
abstract class Page { protected RendererInterface $renderer;
public function __construct(RendererInterface $renderer) { $this->renderer = $renderer; }
abstract public function render(): string; }
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40
| <?php
namespace App\Rendering\Pages;
class ArticlePage extends Page { protected string $title; protected string $content; protected array $links = [];
public function setTitle(string $title): self { $this->title = $title; return $this; }
public function setContent(string $content): self { $this->content = $content; return $this; }
public function addLink(string $text, string $url): self { $this->links[] = ['text' => $text, 'url' => $url]; return $this; }
public function render(): string { $output = $this->renderer->renderTitle($this->title); $output .= $this->renderer->renderText($this->content);
foreach ($this->links as $link) { $output .= $this->renderer->renderLink($link['text'], $link['url']); }
return $output; } }
|
测试桥接模式
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44
| <?php
namespace Tests\Unit\Bridge;
use Tests\TestCase; use App\Messaging\Messages\WelcomeMessage; use App\Messaging\Senders\EmailSender; use App\Messaging\Senders\SmsSender; use App\Rendering\Pages\ArticlePage; use App\Rendering\Renderers\HtmlRenderer; use App\Rendering\Renderers\MarkdownRenderer;
class BridgeTest extends TestCase { public function test_message_with_different_senders(): void { $emailSender = new EmailSender(); $smsSender = $this->createMock(\App\Contracts\Messaging\MessageSenderInterface::class);
$emailMessage = new WelcomeMessage($emailSender); $smsMessage = new WelcomeMessage($smsSender);
$this->assertInstanceOf(WelcomeMessage::class, $emailMessage); $this->assertInstanceOf(WelcomeMessage::class, $smsMessage); }
public function test_page_with_different_renderers(): void { $htmlRenderer = new HtmlRenderer(); $markdownRenderer = new MarkdownRenderer();
$htmlPage = new ArticlePage($htmlRenderer); $markdownPage = new ArticlePage($markdownRenderer);
$htmlPage->setTitle('Test Title')->setContent('Test Content'); $markdownPage->setTitle('Test Title')->setContent('Test Content');
$htmlOutput = $htmlPage->render(); $markdownOutput = $markdownPage->render();
$this->assertStringContainsString('<h1>Test Title</h1>', $htmlOutput); $this->assertStringContainsString('# Test Title', $markdownOutput); } }
|
最佳实践
1. 优先组合而非继承
1 2 3 4 5 6 7 8 9 10 11
| <?php
class GoodBridge { protected ImplementationInterface $implementation;
public function __construct(ImplementationInterface $implementation) { $this->implementation = $implementation; } }
|
2. 接口设计要稳定
1 2 3 4 5 6
| <?php
interface StableInterface { public function coreOperation(): mixed; }
|
3. 抽象层应委托给实现
1 2 3 4 5 6 7 8 9 10 11
| <?php
abstract class Abstraction { protected ImplementationInterface $implementation;
public function operation(): string { return $this->implementation->operationImplementation(); } }
|
总结
Laravel 13 的桥接模式提供了一种灵活的方式来分离抽象和实现。通过合理使用桥接模式,可以创建可扩展、可维护的系统架构,同时支持多个维度的独立变化。