设计模式在 PHP 中的应用
设计模式是面向对象编程中经过验证的解决方案模板,用于解决特定上下文中反复出现的设计问题。在 PHP 项目中合理运用设计模式可以提高代码的可复用性、可扩展性和可维护性。本节结合 PHP 8.x 的新特性,详解最常用的设计模式及其在 PHP 中的实现方式。
基础概念
设计模式分类
| 分类 | 模式 | 目的 |
|---|---|---|
| 创建型 | 单例、工厂、抽象工厂、建造者、原型 | 对象创建机制 |
| 结构型 | 适配器、装饰器、代理、外观、桥接、组合、享元 | 类和对象的组合 |
| 行为型 | 策略、观察者、命令、模板方法、迭代器、状态、责任链 | 对象间的通信 |
单例模式(Singleton)
基本实现
php
<?php
declare(strict_types=1);
namespace App\Core;
final class DatabaseConnection
{
private static ?self $instance = null;
private function __construct(
private readonly string $dsn,
private readonly string $username,
private readonly string $password,
) {
// 私有构造函数,禁止外部实例化
}
public static function getInstance(
string $dsn = '',
string $username = '',
string $password = '',
): self {
if (self::$instance === null) {
self::$instance = new self($dsn, $username, $password);
}
return self::$instance;
}
// 禁止克隆
private function __clone(): void {}
// 禁止反序列化
public function __wakeup(): void
{
throw new \RuntimeException('Cannot unserialize singleton');
}
public function query(string $sql, array $params = []): array
{
// 数据库查询实现
return [];
}
}使用枚举实现单例(PHP 8.1+)
php
<?php
declare(strict_types=1);
namespace App\Core;
/**
* PHP 8.1+ 枚举天然支持单例特性
* 枚举的 case 本身就是单例实例
*/
enum DatabaseConfig: string
{
case Development = 'mysql://localhost/dev_db';
case Staging = 'mysql://staging.example.com/staging_db';
case Production = 'mysql://prod.example.com/prod_db';
public function getDsn(): string
{
return $this->value;
}
public function getHost(): string
{
return parse_url($this->value, PHP_URL_HOST);
}
public function getDatabase(): string
{
$path = parse_url($this->value, PHP_URL_PATH);
return str_replace('/', '', $path);
}
}
// 使用
$config = DatabaseConfig::Production;
echo $config->getHost(); // prod.example.com
echo $config->getDatabase(); // prod_db单例模式的使用建议
单例模式会引入全局状态,使代码难以测试。推荐通过依赖注入容器管理单例生命周期,而非手动实现单例模式。
工厂模式(Factory)
简单工厂
php
<?php
declare(strict_types=1);
namespace App\Factory;
use App\Notification\EmailNotification;
use App\Notification\SmsNotification;
use App\Notification\PushNotification;
use App\Notification\NotificationInterface;
enum NotificationType: string
{
case Email = 'email';
case Sms = 'sms';
case Push = 'push';
}
final class NotificationFactory
{
public static function create(NotificationType $type): NotificationInterface
{
return match ($type) {
NotificationType::Email => new EmailNotification(),
NotificationType::Sms => new SmsNotification(),
NotificationType::Push => new PushNotification(),
};
}
/**
* 使用命名参数创建带配置的通知实例
*/
public static function createWithConfig(
NotificationType $type,
array $config = [],
): NotificationInterface {
$notification = self::create($type);
if (isset($config['recipient'])) {
$notification->setRecipient($config['recipient']);
}
return $notification;
}
}抽象工厂
php
<?php
declare(strict_types=1);
namespace App\Factory\Logging;
interface LoggerFactoryInterface
{
public function createLogger(string $channel): LoggerInterface;
public function createHandler(string $type): HandlerInterface;
public function createFormatter(string $format): FormatterInterface;
}
class MonologFactory implements LoggerFactoryInterface
{
public function createLogger(string $channel): LoggerInterface
{
return new \Monolog\Logger($channel);
}
public function createHandler(string $type): HandlerInterface
{
return match ($type) {
'stream' => new \Monolog\Handler\StreamHandler(
'php://stdout',
\Monolog\Level::Debug,
),
'file' => new \Monolog\Handler\RotatingFileHandler(
storage_path('logs/app.log'),
7,
),
default => throw new \InvalidArgumentException("Unknown handler: {$type}"),
};
}
public function createFormatter(string $format): FormatterInterface
{
return match ($format) {
'json' => new \Monolog\Formatter\JsonFormatter(),
'line' => new \Monolog\Formatter\LineFormatter(),
default => throw new \InvalidArgumentException("Unknown format: {$format}"),
};
}
}策略模式(Strategy)
基本实现
php
<?php
declare(strict_types=1);
namespace App\Pricing;
interface PricingStrategyInterface
{
/**
* 计算价格
*
* @param float $basePrice 原价
* @param array<string, mixed> $context 上下文信息
*/
public function calculate(float $basePrice, array $context): float;
}
class RegularPricingStrategy implements PricingStrategyInterface
{
public function calculate(float $basePrice, array $context): float
{
return $basePrice;
}
}
class MemberDiscountStrategy implements PricingStrategyInterface
{
public function __construct(
private readonly float $discountRate = 0.1,
) {}
public function calculate(float $basePrice, array $context): float
{
return $basePrice * (1 - $this->discountRate);
}
}
class FlashSaleStrategy implements PricingStrategyInterface
{
public function __construct(
private readonly float $salePrice,
) {}
public function calculate(float $basePrice, array $context): float
{
return min($basePrice, $this->salePrice);
}
}
final class PricingContext
{
public function __construct(
private PricingStrategyInterface $strategy,
) {}
public function setStrategy(PricingStrategyInterface $strategy): void
{
$this->strategy = $strategy;
}
public function getPrice(float $basePrice, array $context = []): float
{
return $this->strategy->calculate($basePrice, $context);
}
}使用枚举实现策略选择
php
<?php
declare(strict_types=1);
namespace App\Pricing;
enum PricingType: string
{
case Regular = 'regular';
case MemberDiscount = 'member';
case FlashSale = 'flash_sale';
public function createStrategy(): PricingStrategyInterface
{
return match ($this) {
self::Regular => new RegularPricingStrategy(),
self::MemberDiscount => new MemberDiscountStrategy(),
self::FlashSale => new FlashSaleStrategy(salePrice: 99.00),
};
}
}
// 使用
$type = PricingType::from('member');
$context = new PricingContext($type->createStrategy());
$price = $context->getPrice(100.00);观察者模式(Observer)
SPL 接口实现
php
<?php
declare(strict_types=1);
namespace App\Event;
/**
* 主题(被观察者)
*/
final class UserEventSubject implements \SplSubject
{
/** @var array<int, \SplObserver> */
private array $observers = [];
private array $eventData = [];
public function attach(\SplObserver $observer): void
{
$id = spl_object_id($observer);
$this->observers[$id] = $observer;
}
public function detach(\SplObserver $observer): void
{
$id = spl_object_id($observer);
unset($this->observers[$id]);
}
public function notify(): void
{
foreach ($this->observers as $observer) {
$observer->update($this);
}
}
public function emit(string $eventName, array $data): void
{
$this->eventData = [
'name' => $eventName,
'data' => $data,
'timestamp' => new \DateTimeImmutable(),
];
$this->notify();
}
public function getEventData(): array
{
return $this->eventData;
}
}自定义事件调度器
php
<?php
declare(strict_types=1);
namespace App\Event;
interface EventListenerInterface
{
public function handle(array $event): void;
}
interface EventDispatcherInterface
{
public function listen(string $event, EventListenerInterface $listener): void;
public function dispatch(string $event, array $data = []): void;
public function removeListener(string $event, EventListenerInterface $listener): void;
}
final class EventDispatcher implements EventDispatcherInterface
{
/** @var array<string, array<int, EventListenerInterface>> */
private array $listeners = [];
public function listen(string $event, EventListenerInterface $listener): void
{
$this->listeners[$event][] = $listener;
}
public function dispatch(string $event, array $data = []): void
{
if (!isset($this->listeners[$event])) {
return;
}
foreach ($this->listeners[$event] as $listener) {
$listener->handle(array_merge(['event' => $event], $data));
}
}
public function removeListener(string $event, EventListenerInterface $listener): void
{
if (!isset($this->listeners[$event])) {
return;
}
$this->listeners[$event] = array_filter(
$this->listeners[$event],
fn (EventListenerInterface $l) => $l !== $listener,
);
}
}
// 具体监听器
final class SendWelcomeEmailListener implements EventListenerInterface
{
public function __construct(
private readonly \App\Mail\MailerInterface $mailer,
) {}
public function handle(array $event): void
{
$email = $event['data']['email'] ?? '';
$this->mailer->send($email, 'Welcome!', 'Welcome to our platform!');
}
}
final class CreateUserLogListener implements EventListenerInterface
{
public function __construct(
private readonly \Psr\Log\LoggerInterface $logger,
) {}
public function handle(array $event): void
{
$this->logger->info('User registered', $event['data']);
}
}装饰器模式(Decorator)
基本实现
php
<?php
declare(strict_types=1);
namespace App\Log;
interface LogHandlerInterface
{
public function handle(string $message, array $context = []): void;
}
class FileLogHandler implements LogHandlerInterface
{
public function __construct(
private readonly string $logPath,
) {}
public function handle(string $message, array $context = []): void
{
$line = sprintf(
'[%s] %s %s',
date('Y-m-d H:i:s'),
$message,
json_encode($context, JSON_UNESCAPED_UNICODE),
);
file_put_contents($this->logPath, $line . PHP_EOL, FILE_APPEND);
}
}
abstract class LogHandlerDecorator implements LogHandlerInterface
{
public function __construct(
protected readonly LogHandlerInterface $handler,
) {}
}
class TimestampDecorator extends LogHandlerDecorator
{
public function handle(string $message, array $context = []): void
{
$context['timestamp'] = (new \DateTimeImmutable())->format('Y-m-d\TH:i:s.u');
$this->handler->handle($message, $context);
}
}
class ContextDecorator extends LogHandlerDecorator
{
public function __construct(
LogHandlerInterface $handler,
private readonly array $globalContext,
) {
parent::__construct($handler);
}
public function handle(string $message, array $context = []): void
{
$mergedContext = array_merge($this->globalContext, $context);
$this->handler->handle($message, $mergedContext);
}
}
class FilterDecorator extends LogHandlerDecorator
{
public function __construct(
LogHandlerInterface $handler,
private readonly string $minLevel = 'debug',
) {
parent::__construct($handler);
}
public function handle(string $message, array $context = []): void
{
$level = $context['level'] ?? 'debug';
if ($this->shouldLog($level)) {
$this->handler->handle($message, $context);
}
}
private function shouldLog(string $level): bool
{
$levels = ['debug', 'info', 'warning', 'error', 'critical'];
$currentIdx = array_search($this->minLevel, $levels, true);
$messageIdx = array_search($level, $levels, true);
return $messageIdx >= $currentIdx;
}
}
// 使用 - 装饰器可以任意组合
$baseHandler = new FileLogHandler('/var/log/app.log');
$timestamped = new TimestampDecorator($baseHandler);
$withContext = new ContextDecorator($timestamped, [
'app' => 'my-app',
'env' => 'production',
]);
$filtered = new FilterDecorator($withContext, 'info');
$filtered->handle('User login successful', ['level' => 'info', 'user_id' => 123]);仓储模式(Repository)
接口与实现
php
<?php
declare(strict_types=1);
namespace App\Domain\User;
use App\Domain\User\UserId;
interface UserRepositoryInterface
{
public function findById(UserId $id): ?User;
public function findByEmail(string $email): ?User;
public function save(User $user): void;
public function remove(User $user): void;
public function findActive(int $limit = 20, int $offset = 0): array;
public function count(): int;
}php
<?php
declare(strict_types=1);
namespace App\Infrastructure\Persistence;
use App\Domain\User\User;
use App\Domain\User\UserId;
use App\Domain\User\UserRepositoryInterface;
use PDO;
final class PdoUserRepository implements UserRepositoryInterface
{
public function __construct(
private readonly PDO $db,
) {}
public function findById(UserId $id): ?User
{
$stmt = $this->db->prepare('SELECT * FROM users WHERE id = :id');
$stmt->execute(['id' => $id->value()]);
$data = $stmt->fetch(PDO::FETCH_ASSOC);
if ($data === false) {
return null;
}
return $this->hydrate($data);
}
public function findByEmail(string $email): ?User
{
$stmt = $this->db->prepare('SELECT * FROM users WHERE email = :email');
$stmt->execute(['email' => $email]);
$data = $stmt->fetch(PDO::FETCH_ASSOC);
if ($data === false) {
return null;
}
return $this->hydrate($data);
}
public function save(User $user): void
{
$stmt = $this->db->prepare(
'INSERT INTO users (id, email, name, created_at)
VALUES (:id, :email, :name, :created_at)
ON DUPLICATE KEY UPDATE
email = VALUES(email), name = VALUES(name)'
);
$stmt->execute([
'id' => $user->getId()->value(),
'email' => $user->getEmail(),
'name' => $user->getName(),
'created_at' => $user->getCreatedAt()->format('Y-m-d H:i:s'),
]);
}
public function remove(User $user): void
{
$stmt = $this->db->prepare('DELETE FROM users WHERE id = :id');
$stmt->execute(['id' => $user->getId()->value()]);
}
public function findActive(int $limit = 20, int $offset = 0): array
{
$stmt = $this->db->prepare(
'SELECT * FROM users WHERE is_active = 1 ORDER BY created_at DESC LIMIT :limit OFFSET :offset'
);
$stmt->bindValue('limit', $limit, PDO::PARAM_INT);
$stmt->bindValue('offset', $offset, PDO::PARAM_INT);
$stmt->execute();
return array_map(fn (array $row): User => $this->hydrate($row), $stmt->fetchAll(PDO::FETCH_ASSOC));
}
public function count(): int
{
$stmt = $this->db->query('SELECT COUNT(*) FROM users');
return (int) $stmt->fetchColumn();
}
private function hydrate(array $data): User
{
return User::reconstitute(
id: UserId::fromString($data['id']),
email: $data['email'],
name: $data['name'],
createdAt: new \DateTimeImmutable($data['created_at']),
);
}
}适配器模式(Adapter)
基本实现
php
<?php
declare(strict_types=1);
namespace App\Payment;
interface PaymentGatewayInterface
{
public function charge(float $amount, array $params): PaymentResult;
public function refund(string $transactionId, float $amount): RefundResult;
public function getTransactionStatus(string $transactionId): TransactionStatus;
}
final class StripePaymentGateway implements PaymentGatewayInterface
{
public function __construct(
private readonly string $apiKey,
) {}
public function charge(float $amount, array $params): PaymentResult
{
// Stripe API 调用实现
return new PaymentResult(
transactionId: 'txn_' . bin2hex(random_bytes(16)),
status: TransactionStatus::Success,
amount: $amount,
);
}
public function refund(string $transactionId, float $amount): RefundResult
{
// Stripe 退款实现
return new RefundResult(refundId: 'ref_' . bin2hex(random_bytes(8)));
}
public function getTransactionStatus(string $transactionId): TransactionStatus
{
return TransactionStatus::Success;
}
}
/**
* 适配器:将旧的支付系统适配到新接口
*/
final class LegacyPaymentAdapter implements PaymentGatewayInterface
{
public function __construct(
private readonly \App\Legacy\OldPaymentSystem $legacySystem,
) {}
public function charge(float $amount, array $params): PaymentResult
{
$oldResult = $this->legacySystem->processPayment(
$amount * 100, // 旧系统使用分
$params['customer_id'] ?? 0,
$params['description'] ?? '',
);
return new PaymentResult(
transactionId: (string) $oldResult['payment_id'],
status: $oldResult['success'] ? TransactionStatus::Success : TransactionStatus::Failed,
amount: $amount,
);
}
public function refund(string $transactionId, float $amount): RefundResult
{
$result = $this->legacySystem->processRefund((int) $transactionId, $amount * 100);
return new RefundResult(refundId: (string) $result['refund_id']);
}
public function getTransactionStatus(string $transactionId): TransactionStatus
{
$status = $this->legacySystem->checkStatus((int) $transactionId);
return match ($status) {
'completed' => TransactionStatus::Success,
'failed' => TransactionStatus::Failed,
'pending' => TransactionStatus::Pending,
default => TransactionStatus::Unknown,
};
}
}注意事项
避免过度设计
设计模式不是银弹
不要为了使用设计模式而使用设计模式。过度设计会增加不必要的复杂性。仅在确实能解决当前问题或预见明确需求时使用。
- 单例模式:在现代 PHP 中,推荐通过 DI 容器管理单例
- 工厂模式:简单对象创建不需要工厂,直接使用构造函数即可
- 策略模式:当策略只有两种且不会扩展时,简单的 if/else 或 match 更合适
- 观察者模式:现代框架通常内置事件系统,无需自行实现
最佳实践
- 优先使用 PHP 8 特性:枚举、命名参数、match 表达式使模式实现更简洁
- 面向接口编程:依赖注入接口而非具体实现
- 组合优于继承:装饰器和策略模式是组合的典型应用
- 遵循 SOLID 原则:设计模式是 SOLID 原则的具体体现
- 保持简洁:模式实现应尽量简洁,避免不必要的抽象
下一节
继续学习:依赖管理