簡介
PHP 設計模式是對軟件開發中常見問題的可複用解決方案,通過標準化的結構提升代碼的可維護性、擴展性和複用性。
創建型模式(對象創建)
關注對象的創建過程,解決 “如何靈活、安全地生成對象” 的問題。
單例模式(Singleton)
意圖:確保一個類僅有一個實例,並提供全局訪問點。
適用場景:全局配置、數據庫連接池、日誌管理器(需共享狀態)。
實現要點:
- 私有構造函數(禁止外部實例化)。
- 靜態變量保存唯一實例。
- 靜態方法返回實例(延遲初始化)。
class DatabaseConnection {
// 保存唯一實例
private static ?self $instance = null;
private string $connection;
// 私有構造函數(防止外部 new)
private function __construct(string $dsn) {
$this->connection = "Connected to: $dsn";
}
// 靜態方法獲取實例(延遲初始化)
public static function getInstance(string $dsn = 'mysql:host=localhost'): self {
if (self::$instance === null) {
self::$instance = new self($dsn);
}
return self::$instance;
}
// 禁止克隆(防止複製)
private function __clone() {}
// 禁止反序列化(防止外部重建實例)
public function __wakeup() {
throw new \Exception("Singleton cannot be deserialized");
}
public function getConnection(): string {
return $this->connection;
}
}
// 使用示例
$db1 = DatabaseConnection::getInstance();
$db2 = DatabaseConnection::getInstance('mysql:host=backup');
var_dump($db1 === $db2); // 輸出:bool(true)(始終返回同一個實例)
工廠模式(Factory Method)
意圖:定義創建對象的接口,讓子類決定實例化哪個類。
適用場景:對象創建邏輯複雜(如依賴配置、外部服務),需解耦對象創建與使用。
實現要點:
- 抽象工廠類:聲明創建對象的方法(
createProduct())。 - 具體工廠類:實現抽象方法,返回具體產品。
- 抽象產品類:定義產品的公共接口。
// 抽象產品:日誌記錄器
interface Logger {
public function log(string $message): void;
}
// 具體產品:文件日誌
class FileLogger implements Logger {
public function log(string $message): void {
file_put_contents('app.log', $message . PHP_EOL, FILE_APPEND);
}
}
// 具體產品:數據庫日誌
class DbLogger implements Logger {
public function log(string $message): void {
// 模擬寫入數據庫
echo "Logged to DB: $message" . PHP_EOL;
}
}
// 抽象工廠:日誌工廠
abstract class LoggerFactory {
abstract public function createLogger(): Logger;
}
// 具體工廠:文件日誌工廠
class FileLoggerFactory extends LoggerFactory {
public function createLogger(): Logger {
return new FileLogger();
}
}
// 具體工廠:數據庫日誌工廠
class DbLoggerFactory extends LoggerFactory {
public function createLogger(): Logger {
return new DbLogger();
}
}
// 使用示例
$factory = new FileLoggerFactory(); // 切換工廠即可更換日誌類型
$logger = $factory->createLogger();
$logger->log("User logged in");
優勢:客户端只需知道工廠類型,無需關心具體產品的創建細節,便於擴展新的產品類型。
建造者模式(Builder)
意圖:將複雜對象的構建過程與表示分離,允許通過不同步驟構建不同表示。
適用場景:對象包含多個可選屬性(如配置對象、複雜實體類)。
實現要點:
- 產品類:包含複雜屬性的實體。
- 抽象建造者:定義構建步驟(
setPartA()/setPartB())。 - 具體建造者:實現構建步驟,返回產品。
- 指導者(可選):定義構建順序,調用建造者方法。
// 產品類:用户配置
class UserConfig {
public string $theme;
public bool $notifications;
public int $timeout;
public function __toString(): string {
return "Theme: $this->theme, Notifications: " .
($this->notifications ? 'On' : 'Off') .
", Timeout: $this->timeout";
}
}
// 抽象建造者
interface UserConfigBuilder {
public function setTheme(string $theme): self;
public function enableNotifications(bool $enable): self;
public function setTimeout(int $seconds): self;
public function build(): UserConfig;
}
// 具體建造者
class DefaultUserConfigBuilder implements UserConfigBuilder {
private UserConfig $config;
public function __construct() {
$this->reset();
}
private function reset(): void {
$this->config = new UserConfig();
$this->config->theme = 'light'; // 默認主題
$this->config->notifications = true; // 默認開啓通知
$this->config->timeout = 300; // 默認超時5分鐘
}
public function setTheme(string $theme): self {
$this->config->theme = $theme;
return $this;
}
public function enableNotifications(bool $enable): self {
$this->config->notifications = $enable;
return $this;
}
public function setTimeout(int $seconds): self {
$this->config->timeout = $seconds;
return $this;
}
public function build(): UserConfig {
$result = $this->config;
$this->reset(); // 重置以便下次構建
return $result;
}
}
// 使用示例
$builder = new DefaultUserConfigBuilder();
$config = $builder
->setTheme('dark')
->enableNotifications(false)
->setTimeout(600)
->build();
echo $config; // 輸出:Theme: dark, Notifications: Off, Timeout: 600
優勢:避免構造函數參數過多(如 __construct($theme, $notifications, $timeout)),支持鏈式調用,提升代碼可讀性。
結構型模式(類 / 對象組合)
關注類和對象的組合方式,解決 “如何靈活組織類結構” 的問題。
適配器模式(Adapter)
意圖:將不兼容的接口轉換為客户端期望的接口,解決類 / 對象間的接口不匹配問題。
適用場景:複用第三方庫(接口不兼容)、舊系統升級(兼容新舊接口)。
實現要點:
- 目標接口:客户端期望的接口(
Target)。 - 適配者:需要被適配的類(
Adaptee,通常是第三方或舊代碼)。 - 適配器:包裝適配者,實現目標接口。
// 目標接口(客户端期望的日誌格式)
interface ModernLogger {
public function log(string $level, string $message): void;
}
// 適配者(舊日誌類,接口不兼容)
class LegacyLogger {
public function write(string $message): void {
file_put_contents('legacy.log', $message . PHP_EOL, FILE_APPEND);
}
}
// 適配器:將 LegacyLogger 轉換為 ModernLogger 接口
class LegacyLoggerAdapter implements ModernLogger {
private LegacyLogger $adaptee;
public function __construct(LegacyLogger $adaptee) {
$this->adaptee = $adaptee;
}
public function log(string $level, string $message): void {
// 轉換格式:將 level + message 拼接為舊日誌格式
$this->adaptee->write("[$level] $message");
}
}
// 使用示例
$legacyLogger = new LegacyLogger();
$adapter = new LegacyLoggerAdapter($legacyLogger);
$adapter->log('ERROR', 'Database connection failed'); // 實際調用 LegacyLogger::write
優勢:無需修改舊代碼,通過適配器兼容新接口,符合 “開閉原則”。
裝飾器模式(Decorator)
意圖:動態為對象添加額外功能,替代繼承實現靈活擴展。
適用場景:需要為對象添加多個可選功能(如日誌記錄、權限校驗、緩存)。
實現要點:
- 抽象組件:定義核心功能接口(
Component)。 - 具體組件:實現核心功能(
ConcreteComponent)。 - 裝飾器:包裝組件,添加額外功能(
Decorator)。
// 抽象組件:文件存儲接口
interface FileStorage {
public function save(string $path, string $content): void;
public function read(string $path): string;
}
// 具體組件:基礎文件存儲
class BasicFileStorage implements FileStorage {
public function save(string $path, string $content): void {
file_put_contents($path, $content);
}
public function read(string $path): string {
return file_get_contents($path) ?: '';
}
}
// 裝飾器:添加日誌功能
class LoggedFileStorage implements FileStorage {
private FileStorage $storage;
public function __construct(FileStorage $storage) {
$this->storage = $storage;
}
public function save(string $path, string $content): void {
echo "Log: Saving file $path" . PHP_EOL;
$this->storage->save($path, $content);
}
public function read(string $path): string {
echo "Log: Reading file $path" . PHP_EOL;
return $this->storage->read($path);
}
}
// 裝飾器:添加加密功能
class EncryptedFileStorage implements FileStorage {
private FileStorage $storage;
private string $key = 'secret_key';
public function __construct(FileStorage $storage) {
$this->storage = $storage;
}
public function save(string $path, string $content): void {
$encrypted = openssl_encrypt($content, 'AES-256-CBC', $this->key);
$this->storage->save($path, $encrypted);
}
public function read(string $path): string {
$encrypted = $this->storage->read($path);
return openssl_decrypt($encrypted, 'AES-256-CBC', $this->key) ?: '';
}
}
// 使用示例(組合裝飾器)
$storage = new BasicFileStorage();
$loggedStorage = new LoggedFileStorage($storage);
$secureStorage = new EncryptedFileStorage($loggedStorage); // 先加密,再記錄日誌
$secureStorage->save('data.txt', '敏感信息');
echo $secureStorage->read('data.txt'); // 輸出:敏感信息(解密後)
優勢:通過組合而非繼承添加功能,避免類爆炸(如 EncryptedLoggedFileStorage 無需單獨定義)。
代理模式(Proxy)
意圖:為對象提供一個代理,控制對原對象的訪問(如延遲加載、權限校驗)。
適用場景:遠程服務代理(RPC)、資源消耗大的對象(延遲加載)、權限控制。
實現要點:
- 主題接口:定義原對象和代理的公共接口(
Subject)。 - 真實主題:實際提供功能的對象(
RealSubject)。 - 代理:包裝真實主題,添加控制邏輯(
Proxy)。
// 主題接口:用户信息
interface UserInfo {
public function getProfile(): string;
}
// 真實主題:實際從數據庫加載用户信息
class RealUserInfo implements UserInfo {
private int $userId;
public function __construct(int $userId) {
$this->userId = $userId;
// 模擬耗時的數據庫查詢(僅在需要時執行)
sleep(1); // 假設加載需1秒
}
public function getProfile(): string {
return "User $this->userId: 姓名=張三, 郵箱=zhangsan@example.com";
}
}
// 代理:延遲加載真實對象
class UserInfoProxy implements UserInfo {
private ?RealUserInfo $realUser = null;
private int $userId;
public function __construct(int $userId) {
$this->userId = $userId;
}
public function getProfile(): string {
// 延遲加載:僅在需要時創建真實對象
if ($this->realUser === null) {
$this->realUser = new RealUserInfo($this->userId);
}
return $this->realUser->getProfile();
}
}
// 使用示例
$proxy = new UserInfoProxy(1001);
echo "開始獲取用户信息..." . PHP_EOL;
echo $proxy->getProfile(); // 此時才會觸發真實對象的加載(延遲1秒)
優勢:減少資源消耗(如避免啓動時加載所有大對象),提升系統響應速度。
行為型模式(對象交互)
關注對象間的通信與協作,解決 “如何靈活設計對象行為” 的問題。
觀察者模式(Observer)
意圖:定義對象間的一對多依賴關係,當一個對象狀態變化時,所有依賴它的對象自動更新。
適用場景:事件通知(如用户註冊後發送郵件 / 短信)、狀態監控(如庫存變化觸發提醒)。
實現要點:
- 主題(
Subject):維護觀察者列表,提供註冊 / 移除接口,狀態變化時通知所有觀察者。 - 觀察者(
Observer):定義更新接口,接收主題通知。
// 主題接口
interface Subject {
public function attach(Observer $observer): void;
public function detach(Observer $observer): void;
public function notify(): void;
}
// 觀察者接口
interface Observer {
public function update(string $event, mixed $data): void;
}
// 具體主題:用户服務(觸發註冊事件)
class UserService implements Subject {
private array $observers = [];
public function attach(Observer $observer): void {
$this->observers[] = $observer;
}
public function detach(Observer $observer): void {
$this->observers = array_filter($this->observers, fn($o) => $o !== $observer);
}
public function notify(): void {
foreach ($this->observers as $observer) {
$observer->update('user.register', $this->userData);
}
}
private array $userData;
public function registerUser(string $name, string $email): void {
$this->userData = ['name' => $name, 'email' => $email];
$this->notify(); // 註冊後通知觀察者
}
}
// 具體觀察者:郵件通知
class EmailNotifier implements Observer {
public function update(string $event, mixed $data): void {
if ($event === 'user.register') {
$email = $data['email'];
echo "發送註冊郵件到:$email" . PHP_EOL;
}
}
}
// 具體觀察者:短信通知
class SmsNotifier implements Observer {
public function update(string $event, mixed $data): void {
if ($event === 'user.register') {
$phone = '13800138000'; // 假設從用户數據獲取手機號
echo "發送註冊短信到:$phone" . PHP_EOL;
}
}
}
// 使用示例
$userService = new UserService();
$userService->attach(new EmailNotifier());
$userService->attach(new SmsNotifier());
$userService->registerUser('張三', 'zhangsan@example.com');
// 輸出:
// 發送註冊郵件到:zhangsan@example.com
// 發送註冊短信到:13800138000
優勢:解耦主題與觀察者,支持動態添加 / 移除觀察者(如新增微信通知僅需實現 Observer 接口)。
策略模式(Strategy)
意圖:定義一系列算法,將它們封裝為獨立對象,使算法可互換。
適用場景:同一功能有多種實現方式(如支付方式、排序算法),需動態切換。
實現要點:
- 策略接口:定義算法的公共方法(
Strategy)。 - 具體策略:實現策略接口(
ConcreteStrategyA/ConcreteStrategyB)。
上下文:持有策略對象,委託算法執行(Context)。
// 策略接口:支付方式
interface PaymentStrategy {
public function pay(float $amount): string;
}
// 具體策略:支付寶支付
class AlipayStrategy implements PaymentStrategy {
public function pay(float $amount): string {
return "通過支付寶支付 $amount 元";
}
}
// 具體策略:微信支付
class WechatPayStrategy implements PaymentStrategy {
public function pay(float $amount): string {
return "通過微信支付 $amount 元";
}
}
// 上下文:訂單處理
class OrderContext {
private PaymentStrategy $paymentStrategy;
public function setPaymentStrategy(PaymentStrategy $strategy): void {
$this->paymentStrategy = $strategy;
}
public function processPayment(float $amount): string {
return $this->paymentStrategy->pay($amount);
}
}
// 使用示例
$order = new OrderContext();
// 選擇支付寶支付
$order->setPaymentStrategy(new AlipayStrategy());
echo $order->processPayment(99.9); // 輸出:通過支付寶支付 99.9 元
// 動態切換為微信支付
$order->setPaymentStrategy(new WechatPayStrategy());
echo $order->processPayment(199.9); // 輸出:通過微信支付 199.9 元
優勢:算法獨立於上下文,支持運行時動態切換,避免大量 if-else 判斷。
模板方法模式(Template Method)
意圖:定義算法的骨架,將具體步驟延遲到子類實現。
適用場景:多個子類有公共步驟(如初始化→執行→清理),需統一流程。
實現要點:
- 抽象類:定義模板方法(包含固定步驟)和抽象方法(由子類實現)。
- 具體子類:實現抽象方法,完成具體步驟。
// 抽象類:數據導入流程
abstract class DataImporter {
// 模板方法(定義固定流程)
public final function import(string $filePath): void {
$this->validateFile($filePath);
$data = $this->readFile($filePath);
$this->processData($data);
$this->cleanup();
}
// 固定步驟(通用實現)
protected function validateFile(string $filePath): void {
if (!file_exists($filePath)) {
throw new \Exception("文件 $filePath 不存在");
}
}
// 抽象步驟(由子類實現)
abstract protected function readFile(string $filePath): array;
abstract protected function processData(array $data): void;
// 可選步驟(鈎子方法)
protected function cleanup(): void {
// 默認無操作,子類可重寫
}
}
// 具體子類:CSV 數據導入
class CsvImporter extends DataImporter {
protected function readFile(string $filePath): array {
$handle = fopen($filePath, 'r');
$data = [];
while (($row = fgetcsv($handle)) !== false) {
$data[] = $row;
}
fclose($handle);
return $data;
}
protected function processData(array $data): void {
echo "處理 CSV 數據:" . count($data) . " 行" . PHP_EOL;
}
}
// 具體子類:Excel 數據導入
class ExcelImporter extends DataImporter {
protected function readFile(string $filePath): array {
// 假設使用 PhpSpreadsheet 讀取 Excel
$spreadsheet = \PhpOffice\PhpSpreadsheet\IOFactory::load($filePath);
$worksheet = $spreadsheet->getActiveSheet();
return $worksheet->toArray();
}
protected function processData(array $data): void {
echo "處理 Excel 數據:" . count($data) . " 行" . PHP_EOL;
}
// 重寫鈎子方法(清理臨時文件)
protected function cleanup(): void {
echo "清理 Excel 臨時文件" . PHP_EOL;
}
}
// 使用示例
$csvImporter = new CsvImporter();
$csvImporter->import('data.csv'); // 輸出:處理 CSV 數據:100 行
$excelImporter = new ExcelImporter();
$excelImporter->import('data.xlsx'); // 輸出:處理 Excel 數據:200 行;清理 Excel 臨時文件
優勢:統一流程控制,子類僅需關注具體步驟,避免代碼重複。
責任鏈模式(Chain of Responsibility)
意圖:將請求的發送者與接收者解耦,使多個對象都有機會處理請求(形成鏈式傳遞)。
適用場景:多級審批(如請假流程)、中間件(如請求過濾)。
實現要點:
- 處理者接口:定義處理請求和傳遞請求的方法(
Handler)。 - 具體處理者:實現處理邏輯,決定是否傳遞請求(
ConcreteHandlerA/ConcreteHandlerB)。
// 處理者接口
interface RequestHandler {
public function setNext(RequestHandler $handler): RequestHandler;
public function handle(string $request): string;
}
// 抽象處理者(公共邏輯)
abstract class AbstractRequestHandler implements RequestHandler {
private ?RequestHandler $nextHandler = null;
public function setNext(RequestHandler $handler): RequestHandler {
$this->nextHandler = $handler;
return $handler;
}
public function handle(string $request): string {
if ($this->nextHandler !== null) {
return $this->nextHandler->handle($request);
}
return $request;
}
}
// 具體處理者:敏感詞過濾
class SensitiveWordFilter extends AbstractRequestHandler {
public function handle(string $request): string {
$filtered = str_replace(['非法', '違禁'], '***', $request);
return parent::handle($filtered); // 傳遞給下一個處理者
}
}
// 具體處理者:HTML 轉義
class HtmlEscapeFilter extends AbstractRequestHandler {
public function handle(string $request): string {
$escaped = htmlspecialchars($request, ENT_QUOTES);
return parent::handle($escaped);
}
}
// 具體處理者:日誌記錄
class LoggingFilter extends AbstractRequestHandler {
public function handle(string $request): string {
echo "記錄請求:$request" . PHP_EOL;
return parent::handle($request);
}
}
// 使用示例
$chain = new SensitiveWordFilter();
$chain->setNext(new HtmlEscapeFilter())->setNext(new LoggingFilter());
$input = '用户輸入:<script>非法內容</script>';
$result = $chain->handle($input);
echo "最終處理結果:$result";
// 輸出:
// 記錄請求:用户輸入:<script>***內容</script>
// 最終處理結果:用户輸入:<script>***內容</script>
優勢:請求處理邏輯解耦,支持動態調整處理順序或添加新處理者。