動態

詳情 返回 返回

php 設計模式詳解 - 動態 詳情

簡介

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>
// 最終處理結果:用户輸入:&lt;script&gt;***內容&lt;/script&gt;

優勢:請求處理邏輯解耦,支持動態調整處理順序或添加新處理者。

user avatar waluna 頭像 sy_records 頭像 crmeb 頭像 openeuler 頭像
點贊 4 用戶, 點贊了這篇動態!
點贊

Add a new 評論

Some HTML is okay.