多態是繼承、封裝之後 OOP 第三大支柱,但很多 PHP 開發者停留在「知道定義」卻不會「識別適用場景」。本文通過「上班打卡」經典案例,演示三種實現層次的代碼演進,揭示多態的本質:以統一接口屏蔽具體實現差異,實現擴展開放、修改關閉。
class Student { public function class() { echo "學生正在上課!
"; } }
class Office { public function work() { echo "職員正在上班!
"; } }
function doing($obj) {
if ($obj instanceof Student) { $obj->class(); }
elseif ($obj instanceof Office) { $obj->work(); }
else { echo "未知角色!"; }
}
doing(new Student());
doing(new Office());
痛點:每新增一個角色,必須修改 doing() 函數,違反開閉原則;方法名不統一(class/work),調用方需知曉具體類細節。
abstract class Person {
abstract public function working(); // 強制子類實現
}
class Student extends Person {
public function working() { echo "學生正在上課!
"; }
}
class Office extends Person {
public function working() { echo "職員正在上班!
"; }
}
function doing(Person $obj) { // 類型聲明約束
$obj->working();
}
doing(new Student());
doing(new Office());
優勢:
① 編譯期檢查:傳入非 Person 子類直接報錯
② 統一入口:調用方只認 working(),不關心具體類
③ 擴展零侵入:新增 Intern extends Person 完全無需動 doing()
// 無需共同父類,只需擁有 working() 方法
class Student { public function working() { echo "學生正在上課!
"; } }
class Office { public function working() { echo "職員正在上班!
"; } }
function doing($obj) {
if (method_exists($obj, 'working')) {
$obj->working();
} else {
echo "對象不支持打卡操作";
}
}
PHP 作為動態語言,原生支持「鴨子類型」:「如果它走起來像鴨子、叫起來像鴨子,那它就是鴨子」。配合 PHP 8.0+ 的 interface + implements 可獲得靜態分析支持:
interface Workable { public function working(); }
class Student implements Workable { ... }
class Office implements Workable { ... }
function doing(Workable $obj) { $obj->working(); }
本質上是 運行時方法分發:
調用方發送消息 working() → 對象根據自身類型選擇實現 → 返回響應。
這種「晚綁定」特性讓系統具備:
Workable 的對象可無縫替換若「打卡」行為隨時間/狀態變化(如:上班時間「簽到」、下班時間「簽退」、加班「申請」),可引入狀態對象:
interface ClockState { public function handle(Employee $emp); }
class WorkStart implements ClockState {
public function handle(Employee $emp) { echo "簽到成功"; $emp->setState(new Working()); }
}
class WorkEnd implements ClockState {
public function handle(Employee $emp) { echo "簽退成功"; $emp->setState(new OffDuty()); }
}
class Employee {
private ClockState $state;
public function clockIn() { $this->state->handle($this); }
public function setState(ClockState $s) { $this->state = $s; }
}
這正是多態在領域建模中的高級應用:將條件分支轉化為對象多態。
記住:多態不是為了「炫技」,而是為了將「變化點」封裝成獨立決策單元,讓核心流程穩如磐石。