首頁
編程語言
C語言|JAVA編程
Python編程
網頁編程
ASP編程|PHP編程
JSP編程
數據庫
MYSQL數據庫|SqlServer數據庫
Oracle數據庫|DB2數據庫
您现在的位置: 程式碼之家 >> 編程語言 >  >> 網頁程式設計 >> PHP 程式設計 >> PHP 相關知識

PHP 父類呼叫子類方法實作範例與設計模式解析

     編輯:PHP 相關知識

PHP 允許父類呼叫子類定義的方法,這是「模板方法模式」的核心實現方式。本文透過實例解析父類調用子類方法的機制、應用場景與注意事項。

基本實作範例

<?php
/**
 * 基礎應用類 - 父類
 */
class BaseApp {
    /**
     * 模板方法:定義演算法骨架,延遲具體步驟到子類
     */
    protected function _run_action() {
        $action = 'index';  // 預設動作
        // 關鍵:呼叫當前實例 (子類) 的方法
        $this->$action();
    }
}

/**
 * 預設應用類 - 子類
 */
class DefaultApp extends BaseApp {
    /**
     * 子類實作的具體動作
     * 將被父類的 _run_action() 呼叫
     */
    public function index() {
        echo "DefaultApp->index() invoked\n";
    }
    
    /**
     * 公開入口:觸發父類模板方法
     */
    public function go() {
        // 呼叫父類方法,但實際執行子類的 index()
        parent::_run_action();
    }
}

// 執行
$app = new DefaultApp();
$app->go();  // 輸出: DefaultApp->index() invoked

核心機制解析

概念說明
多態$this->$action() 在執行時綁定到實際物件類型 (子類)
晚期靜態綁定PHP 5.3+ static::method() 解決靜態方法繼承問題
實例化主體必須實例化子類,父類抽象不可直接實例化
可見性子類方法至少 protected,父類才能存取

進階模式:模板方法模式 完整實作

<?php
abstract class DataProcessor {
    // === 模板方法 (final 防止覆寫) ===
    final public function process($source) {
        $data = $this->read($source);      // 步驟 1: 讀取 (子類實作)
        $data = $this->transform($data);   // 步驟 2: 轉換 (子類實作)
        $this->write($data);               // 步驟 3: 寫入 (子類實作)
        $this->postProcess();              // 步驟 4: 後處理 (可選,預設空)
    }
    
    // === 抽象方法:強制子類實作 ===
    abstract protected function read($source);
    abstract protected function transform(array $data);
    abstract protected function write(array $data);
    
    // === 鉤子方法:子類可選覆寫 ===
    protected function postProcess() {
        // 預設不做事
    }
}

// CSV 處理器
class CsvProcessor extends DataProcessor {
    protected function read($filepath) {
        $rows = [];
        if (($fp = fopen($filepath, 'r')) !== false) {
            while (($row = fgetcsv($fp)) !== false) {
                $rows[] = $row;
            }
            fclose($fp);
        }
        return $rows;
    }
    
    protected function transform(array $data) {
        // 第一行作為標題
        $header = array_shift($data);
        return array_map(function($row) use ($header) {
            return array_combine($header, $row);
        }, $data);
    }
    
    protected function write(array $data) {
        // 這裡可存入資料庫、API、檔案等
        echo "寫入 " . count($data) . " 筆資料\n";
    }
    
    protected function postProcess() {
        echo "CSV 處理完成,發送通知郵件...\n";
    }
}

// JSON 處理器
class JsonProcessor extends DataProcessor {
    protected function read($filepath) {
        return json_decode(file_get_contents($filepath), true);
    }
    
    protected function transform(array $data) {
        // 統一欄位命名慣例
        return array_map(function($item) {
            return array_change_key_case($item, CASE_LOWER);
        }, $data);
    }
    
    protected function write(array $data) {
        echo "JSON 匯入 " . count($data) . " 筆完成\n";
    }
}

// 使用
$csv = new CsvProcessor();
$csv->process('data.csv');

$json = new JsonProcessor();
$json->process('data.json');

另一經典場景:資料庫 ActiveRecord 基類

<?php
abstract class ActiveRecord {
    protected $table;
    protected $primary_key = 'id';
    protected $fillable = [];
    
    // 模板方法:儲存 (新增/更新)
    public function save() {
        $this->beforeSave();
        
        if ($this->{$this->primary_key}) {
            $this->update();
        } else {
            $this->insert();
        }
        
        $this->afterSave();
    }
    
    // 子類必須實作:取得資料表欄位定義
    abstract protected function getSchema();
    
    // 鉤子方法
    protected function beforeSave() {}
    protected function afterSave() {}
    
    private function insert() {
        $fields = array_intersect_key($this->attributes, array_flip($this->fillable));
        // 執行 INSERT...
        $this->{$this->primary_key} = $this->db->lastInsertId();
    }
    
    private function update() {
        // 執行 UPDATE...
    }
}

class User extends ActiveRecord {
    protected $table = 'users';
    protected $fillable = ['name', 'email', 'password'];
    
    protected function getSchema() {
        return [
            'id' => 'int(11) AI PK',
            'name' => 'varchar(100)',
            'email' => 'varchar(150) UNIQUE',
            'password' => 'varchar(255)',
        ];
    }
    
    protected function beforeSave() {
        if (isset($this->attributes['password'])) {
            $this->attributes['password'] = password_hash($this->attributes['password'], PASSWORD_DEFAULT);
        }
    }
}

$user = new User();
$user->name = 'John';
$user->email = 'john@example.com';
$user->password = 'secret123';
$user->save();  // 自動哈希密碼並插入

常見誤區與最佳實踐

誤區正確理解
「父類呼叫子類」違反依賴倒置這是「好萊塢原則」:Don't call us, we'll call you。父類定義流程,子類填充細節
直接實例化父類也能呼叫子類方法錯誤。必須實例化子類,$this 才指向子類實例
靜態方法也能這樣用靜態方法需用 static::method() (LSB) 而非 self::method()
所有方法都應可被父類呼叫僅將「演算法步驟」暴露給父類,內部輔助方法保持 private

PHP 8.0+ 新特性:屬性驅動模板方法

<?php
#[\Attribute]
class Step {
    public function __construct(public int $order) {}
}

class Processor {
    public function execute() {
        $methods = (new ReflectionClass($this))->getMethods();
        $steps = [];
        
        foreach ($methods as $method) {
            $attrs = $method->getAttributes(Step::class);
            if ($attrs) {
                $steps[$attrs[0]->newInstance()->order] = $method->name;
            }
        }
        
        ksort($steps);
        foreach ($steps as $method) {
            $this->$method();
        }
    }
}

class MyProcessor extends Processor {
    #[Step(10)] protected function stepOne() { echo "Step 1\n"; }
    #[Step(20)] protected function stepTwo() { echo "Step 2\n"; }
    #[Step(30)] protected function stepThree() { echo "Step 3\n"; }
}

(new MyProcessor())->execute();
// 輸出: Step 1 → Step 2 → Step 3 (順序由屬性決定,不需父類硬編碼)

父類呼叫子類方法不是「反模式」,而是模板方法、策略模式、工廠模式等經典設計模式的基石。關鍵在於:父類定義「做什麼」(流程),子類決定「怎麼做」(實作)。

  1. 上一篇文章:
  2. 下一篇文章:
Copyright © 程式碼之家 All Rights Reserved