Laravel 13 装饰器模式深度解析
装饰器模式是一种结构型设计模式,它允许在不修改对象自身代码的情况下动态地给对象添加新的行为。本文将深入探讨 Laravel 13 中装饰器模式的高级用法。
装饰器模式基础
什么是装饰器模式
装饰器模式通过创建包装对象来包裹原始对象,在保持原始对象接口不变的情况下添加额外功能。
1 2 3 4 5 6 7 8
| <?php
namespace App\Contracts;
interface ComponentInterface { public function operation(): mixed; }
|
1 2 3 4 5 6 7 8 9 10 11 12 13
| <?php
namespace App\Components;
use App\Contracts\ComponentInterface;
class ConcreteComponent implements ComponentInterface { public function operation(): mixed { return 'Base operation'; } }
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17
| <?php
namespace App\Decorators;
use App\Contracts\ComponentInterface;
abstract class Decorator implements ComponentInterface { protected ComponentInterface $component;
public function __construct(ComponentInterface $component) { $this->component = $component; }
abstract public function operation(): mixed; }
|
日志装饰器
服务接口
1 2 3 4 5 6 7 8 9 10 11 12
| <?php
namespace App\Contracts\Services;
interface PaymentServiceInterface { public function process(float $amount, array $data = []): PaymentResult;
public function refund(string $transactionId): bool;
public function getStatus(string $transactionId): 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
| <?php
namespace App\Services\Payments;
use App\Contracts\Services\PaymentServiceInterface;
class StripePaymentService implements PaymentServiceInterface { public function process(float $amount, array $data = []): PaymentResult { $charge = \Stripe\Charge::create([ 'amount' => $amount * 100, 'currency' => $data['currency'] ?? 'usd', 'source' => $data['token'], ]);
return new PaymentResult( success: $charge->status === 'succeeded', transactionId: $charge->id, amount: $amount ); }
public function refund(string $transactionId): bool { $refund = \Stripe\Refund::create(['charge' => $transactionId]); return $refund->status === 'succeeded'; }
public function getStatus(string $transactionId): string { $charge = \Stripe\Charge::retrieve($transactionId); return $charge->status; } }
|
日志装饰器
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
| <?php
namespace App\Decorators\Payments;
use App\Contracts\Services\PaymentServiceInterface; use Illuminate\Support\Facades\Log;
class LoggingDecorator implements PaymentServiceInterface { protected PaymentServiceInterface $service;
public function __construct(PaymentServiceInterface $service) { $this->service = $service; }
public function process(float $amount, array $data = []): PaymentResult { Log::info('Payment processing started', [ 'amount' => $amount, 'currency' => $data['currency'] ?? 'usd', ]);
try { $result = $this->service->process($amount, $data);
Log::info('Payment processing completed', [ 'transaction_id' => $result->transactionId, 'success' => $result->success, ]);
return $result; } catch (\Exception $e) { Log::error('Payment processing failed', [ 'amount' => $amount, 'error' => $e->getMessage(), ]);
throw $e; } }
public function refund(string $transactionId): bool { Log::info('Refund initiated', ['transaction_id' => $transactionId]);
$result = $this->service->refund($transactionId);
Log::info('Refund completed', [ 'transaction_id' => $transactionId, 'success' => $result, ]);
return $result; }
public function getStatus(string $transactionId): string { return $this->service->getStatus($transactionId); } }
|
缓存装饰器
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\Decorators\Payments;
use App\Contracts\Services\PaymentServiceInterface; use Illuminate\Support\Facades\Cache;
class CachingDecorator implements PaymentServiceInterface { protected PaymentServiceInterface $service; protected int $cacheTtl = 300;
public function __construct(PaymentServiceInterface $service) { $this->service = $service; }
public function process(float $amount, array $data = []): PaymentResult { return $this->service->process($amount, $data); }
public function refund(string $transactionId): bool { Cache::forget("payment.status.{$transactionId}"); return $this->service->refund($transactionId); }
public function getStatus(string $transactionId): string { return Cache::remember( "payment.status.{$transactionId}", $this->cacheTtl, fn() => $this->service->getStatus($transactionId) ); } }
|
重试装饰器
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
| <?php
namespace App\Decorators\Payments;
use App\Contracts\Services\PaymentServiceInterface; use Illuminate\Support\Facades\Log;
class RetryDecorator implements PaymentServiceInterface { protected PaymentServiceInterface $service; protected int $maxAttempts; protected int $delayMs;
public function __construct( PaymentServiceInterface $service, int $maxAttempts = 3, int $delayMs = 1000 ) { $this->service = $service; $this->maxAttempts = $maxAttempts; $this->delayMs = $delayMs; }
public function process(float $amount, array $data = []): PaymentResult { $attempts = 0; $lastException = null;
while ($attempts < $this->maxAttempts) { try { return $this->service->process($amount, $data); } catch (\Exception $e) { $lastException = $e; $attempts++;
if ($attempts < $this->maxAttempts) { Log::warning('Payment attempt failed, retrying', [ 'attempt' => $attempts, 'error' => $e->getMessage(), ]);
usleep($this->delayMs * 1000); } } }
throw $lastException; }
public function refund(string $transactionId): bool { return $this->service->refund($transactionId); }
public function getStatus(string $transactionId): string { return $this->service->getStatus($transactionId); } }
|
验证装饰器
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
| <?php
namespace App\Decorators\Payments;
use App\Contracts\Services\PaymentServiceInterface; use Illuminate\Support\Facades\Validator; use InvalidArgumentException;
class ValidationDecorator implements PaymentServiceInterface { protected PaymentServiceInterface $service;
protected array $processRules = [ 'amount' => 'required|numeric|min:0.01|max:999999.99', 'currency' => 'sometimes|string|size:3', 'token' => 'required|string', ];
public function __construct(PaymentServiceInterface $service) { $this->service = $service; }
public function process(float $amount, array $data = []): PaymentResult { $data['amount'] = $amount;
$validator = Validator::make($data, $this->processRules);
if ($validator->fails()) { throw new InvalidArgumentException( 'Invalid payment data: ' . json_encode($validator->errors()->all()) ); }
return $this->service->process($amount, $data); }
public function refund(string $transactionId): bool { if (empty($transactionId) || !preg_match('/^ch_[a-zA-Z0-9]+$/', $transactionId)) { throw new InvalidArgumentException('Invalid transaction ID format'); }
return $this->service->refund($transactionId); }
public function getStatus(string $transactionId): string { return $this->service->getStatus($transactionId); } }
|
限流装饰器
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
| <?php
namespace App\Decorators\Payments;
use App\Contracts\Services\PaymentServiceInterface; use Illuminate\Support\Facades\Cache; use RuntimeException;
class RateLimitDecorator implements PaymentServiceInterface { protected PaymentServiceInterface $service; protected int $maxRequests; protected int $windowSeconds;
public function __construct( PaymentServiceInterface $service, int $maxRequests = 100, int $windowSeconds = 60 ) { $this->service = $service; $this->maxRequests = $maxRequests; $this->windowSeconds = $windowSeconds; }
public function process(float $amount, array $data = []): PaymentResult { $this->checkRateLimit($data['user_id'] ?? 'default');
return $this->service->process($amount, $data); }
public function refund(string $transactionId): bool { return $this->service->refund($transactionId); }
public function getStatus(string $transactionId): string { return $this->service->getStatus($transactionId); }
protected function checkRateLimit(string $identifier): void { $key = "payment.rate_limit.{$identifier}"; $current = Cache::get($key, 0);
if ($current >= $this->maxRequests) { throw new RuntimeException('Rate limit exceeded. Please try again later.'); }
Cache::put($key, $current + 1, $this->windowSeconds); } }
|
审计装饰器
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
| <?php
namespace App\Decorators\Payments;
use App\Contracts\Services\PaymentServiceInterface; use App\Models\AuditLog; use Illuminate\Support\Facades\Auth;
class AuditDecorator implements PaymentServiceInterface { protected PaymentServiceInterface $service;
public function __construct(PaymentServiceInterface $service) { $this->service = $service; }
public function process(float $amount, array $data = []): PaymentResult { $result = $this->service->process($amount, $data);
$this->logAudit('payment.process', [ 'amount' => $amount, 'transaction_id' => $result->transactionId, 'success' => $result->success, 'user_id' => Auth::id(), ]);
return $result; }
public function refund(string $transactionId): bool { $result = $this->service->refund($transactionId);
$this->logAudit('payment.refund', [ 'transaction_id' => $transactionId, 'success' => $result, 'user_id' => Auth::id(), ]);
return $result; }
public function getStatus(string $transactionId): string { return $this->service->getStatus($transactionId); }
protected function logAudit(string $action, array $data): void { AuditLog::create([ 'action' => $action, 'data' => $data, 'user_id' => Auth::id(), 'ip_address' => request()->ip(), 'user_agent' => request()->userAgent(), ]); } }
|
装饰器组合
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
| <?php
namespace App\Services\Payments;
use App\Contracts\Services\PaymentServiceInterface; use App\Decorators\Payments\{ LoggingDecorator, CachingDecorator, RetryDecorator, ValidationDecorator, RateLimitDecorator, AuditDecorator };
class PaymentServiceFactory { public static function create(): PaymentServiceInterface { $service = new StripePaymentService();
$service = new ValidationDecorator($service); $service = new RateLimitDecorator($service, 100, 60); $service = new RetryDecorator($service, 3, 1000); $service = new CachingDecorator($service); $service = new AuditDecorator($service); $service = new LoggingDecorator($service);
return $service; } }
|
消息队列装饰器
1 2 3 4 5 6 7 8 9 10 11 12
| <?php
namespace App\Contracts\Services;
interface MessageQueueInterface { public function publish(string $queue, mixed $message): bool;
public function consume(string $queue, callable $handler): void;
public function acknowledge(mixed $message): bool; }
|
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
| <?php
namespace App\Decorators\Queue;
use App\Contracts\Services\MessageQueueInterface; use Illuminate\Support\Facades\Log;
class QueueLoggingDecorator implements MessageQueueInterface { protected MessageQueueInterface $queue;
public function __construct(MessageQueueInterface $queue) { $this->queue = $queue; }
public function publish(string $queue, mixed $message): bool { Log::debug('Message published', [ 'queue' => $queue, 'message_id' => $message['id'] ?? null, ]);
return $this->queue->publish($queue, $message); }
public function consume(string $queue, callable $handler): void { $this->queue->consume($queue, function ($message) use ($handler, $queue) { Log::debug('Message received', [ 'queue' => $queue, 'message_id' => $message['id'] ?? null, ]);
return $handler($message); }); }
public function acknowledge(mixed $message): bool { $result = $this->queue->acknowledge($message);
Log::debug('Message acknowledged', [ 'message_id' => $message['id'] ?? null, 'success' => $result, ]);
return $result; } }
|
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
| <?php
namespace App\Decorators\Queue;
use App\Contracts\Services\MessageQueueInterface; use Illuminate\Support\Facades\Metrics;
class QueueMetricsDecorator implements MessageQueueInterface { protected MessageQueueInterface $queue;
public function __construct(MessageQueueInterface $queue) { $this->queue = $queue; }
public function publish(string $queue, mixed $message): bool { $start = microtime(true);
try { $result = $this->queue->publish($queue, $message);
Metrics::increment('queue.publish.success', ['queue' => $queue]); Metrics::timing('queue.publish.duration', (microtime(true) - $start) * 1000, ['queue' => $queue]);
return $result; } catch (\Exception $e) { Metrics::increment('queue.publish.error', ['queue' => $queue]); throw $e; } }
public function consume(string $queue, callable $handler): void { $this->queue->consume($queue, function ($message) use ($handler, $queue) { $start = microtime(true);
try { $result = $handler($message);
Metrics::increment('queue.consume.success', ['queue' => $queue]); Metrics::timing('queue.consume.duration', (microtime(true) - $start) * 1000, ['queue' => $queue]);
return $result; } catch (\Exception $e) { Metrics::increment('queue.consume.error', ['queue' => $queue]); throw $e; } }); }
public function acknowledge(mixed $message): bool { return $this->queue->acknowledge($message); } }
|
HTTP 客户端装饰器
1 2 3 4 5 6 7 8 9 10 11 12 13 14
| <?php
namespace App\Contracts\Http;
interface HttpClientInterface { public function get(string $url, array $options = []): HttpResponse;
public function post(string $url, array $data = [], array $options = []): HttpResponse;
public function put(string $url, array $data = [], array $options = []): HttpResponse;
public function delete(string $url, array $options = []): HttpResponse; }
|
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
| <?php
namespace App\Decorators\Http;
use App\Contracts\Http\HttpClientInterface; use Illuminate\Support\Facades\Cache;
class CacheDecorator implements HttpClientInterface { protected HttpClientInterface $client; protected int $defaultTtl; protected array $cacheableMethods = ['GET'];
public function __construct(HttpClientInterface $client, int $defaultTtl = 3600) { $this->client = $client; $this->defaultTtl = $defaultTtl; }
public function get(string $url, array $options = []): HttpResponse { $ttl = $options['cache_ttl'] ?? $this->defaultTtl;
if ($ttl > 0) { $key = $this->getCacheKey('GET', $url, $options);
return Cache::remember($key, $ttl, function () use ($url, $options) { return $this->client->get($url, $options); }); }
return $this->client->get($url, $options); }
public function post(string $url, array $data = [], array $options = []): HttpResponse { return $this->client->post($url, $data, $options); }
public function put(string $url, array $data = [], array $options = []): HttpResponse { return $this->client->put($url, $data, $options); }
public function delete(string $url, array $options = []): HttpResponse { return $this->client->delete($url, $options); }
protected function getCacheKey(string $method, string $url, array $options): string { return 'http:' . md5($method . $url . json_encode($options)); } }
|
依赖注入配置
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
| <?php
namespace App\Providers;
use Illuminate\Support\ServiceProvider; use App\Contracts\Services\PaymentServiceInterface; use App\Services\Payments\StripePaymentService; use App\Decorators\Payments\{ LoggingDecorator, CachingDecorator, RetryDecorator, ValidationDecorator, AuditDecorator };
class DecoratorServiceProvider extends ServiceProvider { public function register(): void { $this->app->bind(PaymentServiceInterface::class, function ($app) { $service = new StripePaymentService();
$service = new ValidationDecorator($service); $service = new RetryDecorator($service, 3, 1000); $service = new CachingDecorator($service); $service = new AuditDecorator($service); $service = new LoggingDecorator($service);
return $service; }); } }
|
测试装饰器
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
| <?php
namespace Tests\Unit\Decorators;
use Tests\TestCase; use App\Contracts\Services\PaymentServiceInterface; use App\Decorators\Payments\LoggingDecorator; use App\Decorators\Payments\CachingDecorator; use Mockery;
class PaymentDecoratorTest extends TestCase { public function test_logging_decorator_logs_operations(): void { $mockService = Mockery::mock(PaymentServiceInterface::class); $mockService->shouldReceive('process') ->once() ->andReturn(new PaymentResult(true, 'txn_123', 100.00));
Log::shouldReceive('info')->twice();
$decorator = new LoggingDecorator($mockService); $result = $decorator->process(100.00, ['token' => 'tok_test']);
$this->assertTrue($result->success); }
public function test_caching_decorator_caches_status(): void { $mockService = Mockery::mock(PaymentServiceInterface::class); $mockService->shouldReceive('getStatus') ->once() ->with('txn_123') ->andReturn('succeeded');
$decorator = new CachingDecorator($mockService);
$status1 = $decorator->getStatus('txn_123'); $status2 = $decorator->getStatus('txn_123');
$this->assertEquals('succeeded', $status1); $this->assertEquals('succeeded', $status2); }
public function test_retry_decorator_retries_on_failure(): void { $mockService = Mockery::mock(PaymentServiceInterface::class); $mockService->shouldReceive('process') ->twice() ->andThrow(new \Exception('Network error')) ->once() ->andReturn(new PaymentResult(true, 'txn_123', 100.00));
$decorator = new RetryDecorator($mockService, 3, 0); $result = $decorator->process(100.00, ['token' => 'tok_test']);
$this->assertTrue($result->success); } }
|
最佳实践
1. 保持装饰器单一职责
1 2 3 4 5 6 7 8 9 10 11 12
| <?php
namespace App\Decorators;
class LoggingOnlyDecorator implements ServiceInterface { public function operation(): mixed { Log::info('Operation called'); return $this->service->operation(); } }
|
2. 装饰器顺序很重要
1 2 3 4 5 6 7 8 9
| <?php
$service = new ValidationDecorator( new RateLimitDecorator( new RetryDecorator( new BasePaymentService() ) ) );
|
3. 接口一致性
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18
| <?php
namespace App\Decorators;
abstract class AbstractDecorator implements ServiceInterface { protected ServiceInterface $service;
public function __construct(ServiceInterface $service) { $this->service = $service; }
public function __call(string $method, array $arguments): mixed { return $this->service->{$method}(...$arguments); } }
|
总结
Laravel 13 的装饰器模式提供了一种灵活的方式来扩展对象功能。通过合理使用装饰器模式,可以在不修改原有代码的情况下添加新功能,提高代码的可维护性和可扩展性。