在學習設計模式(特別是單例模式)時,`static` 關鍵字的行為往往是困惑的來源。本文將從語言規格、記憶體模型、繼承多態三個維度,徹底釐清 `static`、`self`、`static`(晚期靜態綁定)的本質區別,並附帶可直接執行的驗證代碼。
根據 PHP 官方手冊:
將類屬性或方法聲明為 static,使其無需實例化類即可訪問。靜態屬性不能通過已實例化的對象訪問(但靜態方法可以)。
引入版本:PHP 5.3.0
核心問題:`self::` 在繼承鏈中總是解析到「定義該方法的類」,而非「實際調用的類」。這導致靜態方法無法實現多態。
定義基類 `Vehicle` 與子類 `Sedan`,均擁有靜態屬性 `$name`。
<?php
class Vehicle {
protected static $name = 'This is a Vehicle';
public static function what_vehicle() {
echo get_called_class() . "\n";
echo self::$name;
}
}
class Sedan extends Vehicle {
protected static $name = 'This is a Sedan';
}
Sedan::what_vehicle();
輸出:
Sedan This is a Vehicle
解析:`get_called_class()` 正確返回 `Sedan`,但 `self::$name` 綁定在 `Vehicle` 定義處,故輸出父類屬性。
<?php
class Vehicle {
protected static $name = 'This is a Vehicle';
public static function what_vehicle() {
echo get_called_class() . "\n";
echo static::$name; // 關鍵改動
}
}
class Sedan extends Vehicle {
protected static $name = 'This is a Sedan';
}
Sedan::what_vehicle();
輸出:
Sedan This is a Sedan
解析:`static::$name` 在運行時解析為實際調用類 `Sedan`,實現了靜態屬性的「多態」。
<?php
class A { public static $x = 1; }
class B extends A { }
class C extends A { }
B::$x = 2;
echo A::$x; // 2 —— 父子類共享同一記憶體槽位!
echo C::$x; // 2
對策:若需子類獨立狀態,必須在子類重新聲明該屬性(如上文 `$name` 例子),或改用實例屬性 + 單例模式。
<?php
class Foo {
public $bar = 'baz';
public static function test() {
echo $this->bar; // Fatal Error: Using $this when not in object context
}
}
<?php
abstract class Singleton {
private static $instances = [];
final protected function __construct() {}
final protected function __clone() {}
final public static function getInstance() {
$cls = get_called_class(); // 關鍵:獲取實際調用的子類名
if (!isset(self::$instances[$cls])) {
self::$instances[$cls] = new $cls();
}
return self::$instances[$cls];
}
}
class Config extends Singleton { }
class Logger extends Singleton { }
// Config::getInstance() 與 Logger::getInstance() 互不干擾,各自維護單例
| 特性 | self:: | static:: (LSB) |
|---|---|---|
| 綁定時機 | 編譯期(定義所在類) | 運行期(實際調用類) |
| 繼承多態 | 不支援 | 支援 |
| 適用場景 | 調用父類確定的靜態成員、常量 | 需要子類覆寫的靜態屬性/方法、單例基類 |
| 性能 | 極快(無運行期查找) | 微乎其微的開銷(查找調用棧) |
理解 `static`、`self`、`static::` 的區別,關鍵在於掌握 「定義時綁定」vs「調用時綁定」 的本質差異。現代 PHP 開發中,優先使用 `static::` 實現靜態多態,僅在明確需要鎖定父類實現時使用 `self::`。配合 `get_called_class()`,可構建出極具擴展性的框架級基類(如單例、工廠、註冊表模式)。