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

PHP 父類呼叫子類方法實作模式:模板方法、Late Static Binding 與實戰範例

     編輯:PHP 相關知識

前言

在繼承體系中,父類定義演算法骨架,將具體步驟延遲到子類實作,是「模板方法模式」的核心。PHP 支援在父類中透過 $this->method() 呼叫子類覆寫的方法(動態派發),也支援靜態語境下的 static::method()(後期靜態綁定)。本文系統解析兩種機制與實戰應用。

動態派發:$this->method()

非靜態方法呼叫預設即為動態派發:實際執行的是物件實際類別的方法。

abstract class BaseApp {
    final public function run(): void {
        $this->initialize();   // 步驟 1:子類實作
        $this->execute();      // 步驟 2:子類實作
        $this->finalize();     // 步驟 3:子類實作
    }
    abstract protected function initialize(): void;
    abstract protected function execute(): void;
    abstract protected function finalize(): void;
}

class DefaultApp extends BaseApp {
    protected function initialize(): void { echo "Init\n"; }
    protected function execute(): void    { echo "Execute\n"; }
    protected function finalize(): void   { echo "Finalize\n"; }
}

$app = new DefaultApp();
$app->run();
// 輸出:Init → Execute → Finalize

關鍵點:

  • 父類方法宣告 final 防止子類破壞流程。
  • 步驟方法宣告 protected abstract 強制子類實作。
  • $this-> 會解析到 DefaultApp 的實作。

靜態語境:static::method()(Late Static Binding)

PHP 5.3+ 引入 static:: 關鍵字,在靜態方法中解析為實際被呼叫的類別,而非定義該方法的類別。

class ParentModel {
    public static function getTable(): string {
        return static::TABLE; // LSB:解析為 ChildModel::TABLE
    }
    public static function findAll(): array {
        $table = static::getTable();
        return Database::query("SELECT * FROM $table");
    }
}

class UserModel extends ParentModel {
    protected const TABLE = 'users';
}
class OrderModel extends ParentModel {
    protected const TABLE = 'orders';
}

UserModel::findAll();  // SELECT * FROM users
OrderModel::findAll(); // SELECT * FROM orders

對照:self:: vs static::

class A {
    public static function who() { echo __CLASS__ . "\n"; }
    public static function testSelf()   { self::who(); }   // 編譯期綁定 → A
    public static function testStatic() { static::who(); } // 執行期綁定 → 實際類別
}
class B extends A {
    public static function who() { echo __CLASS__ . "\n"; }
}
B::testSelf();   // A
B::testStatic(); // B

實戰範例:ActiveRecord 基類

abstract class ActiveRecord {
    protected static string $table = '';
    protected array $attributes = [];

    public static function find(int $id): ?self {
        $sql = "SELECT * FROM " . static::$table . " WHERE id = ?";
        $row = Database::fetch($sql, [$id]);
        return $row ? new static($row) : null; // new static() 也是 LSB
    }

    public function __construct(array $data = []) {
        $this->attributes = $data;
    }

    public function save(): bool {
        if (empty($this->attributes['id'])) {
            return $this->insert();
        }
        return $this->update();
    }

    protected function insert(): bool {
        $cols = implode(',', array_keys($this->attributes));
        $ph   = ':' . implode(',:', array_keys($this->attributes));
        $sql  = "INSERT INTO " . static::$table . " ($cols) VALUES ($ph)";
        return Database::execute($sql, $this->attributes);
    }
    // update/delete 同理...
}

class User extends ActiveRecord {
    protected static string $table = 'users';
}

$user = User::find(1);
$user->email = 'new@example.com';
$user->save();

父類建構子強制子類實作介面

interface Configurable {
    public function getConfig(): array;
}

abstract class ServiceBase {
    public function __construct() {
        if (!$this instanceof Configurable) {
            throw new LogicException(static::class . ' must implement Configurable');
        }
        $this->configure($this->getConfig());
    }
    abstract protected function configure(array $config): void;
}

class EmailService extends ServiceBase implements Configurable {
    public function getConfig(): array { return ['host' => 'smtp.example.com']; }
    protected function configure(array $config): void { /* ... */ }
}

常見陷阱與避坑

  1. 靜態方法中呼叫 $this->:報錯 Using $this when not in object context。靜態語境請用 static:: 或 self::。
  2. 建構子中呼叫虛擬方法:子類屬性尚未初始化,可能導致空值。建議改用 init() 方法在建構後手動呼叫,或採用工廠模式。
  3. private 方法無法被子類覆寫:父類 private function step() 子類定義同名方法會視為新方法,動態派發失效。請用 protected。
  4. new static() 需要 PHP 5.3+,舊版本請用 get_called_class()。

結語

父類呼叫子類方法是框架設計(Laravel、ThinkPHP、Yii 等)的基石。掌握 動態派發($this->)處理實例流程、後期靜態綁定(static::)處理靜態工廠/ActiveRecord,並嚴守 final/protected abstract 契約,即能構建可擴充、可測試的繼承體系。

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