PHP 面向對象篇:繼承、封裝與多態
2023-06-03 加入收藏
1、繼承
所謂繼承,指的是子類可以通過繼承的方式訪問父類的屬性和方法(protected
或者 public
方式定義),在 PHP 中,繼承通過 extends
關鍵字實現,我們以上篇教程編寫的 Car
類為例,編寫一個實現該類的子類 Benz
(仍然在 class.php
中定義):
class Benz extends Car
{
public function __construct($seats = 5, $doors = 4, $engine = 1)
{
$this->brand = '奔馳';
// $this->setBrand('奔馳'); // 也可以通過該方法設置
parent::__construct($this->brand, $seats, $doors, $engine);
}
}
這里 extends Car
的含義就是 Benz
繼承自 Car
,是它的子類,相對的,Car
是 Benz
的父類。
在子類 Benz
的構造函數中,我們將品牌設置為「奔馳」,然后通過 parent::__construct
調用父類的構造函數進行初始化(調用父類的同名方法需要通過 parent::
進行調用,否則 PHP 會不知道調用父類還是子類的方法),這樣,初始化 Benz
對象時,就無須傳入品牌參數了。
可以看到,在子類中可以通過 $this
對象直接訪問父類定義的屬性和方法,前提是該屬性或方法的可見性是 protected
或者 public
級別,如果試圖訪問 private
聲明的屬性或方法,PhpStorm 會警告:
運行代碼也會報錯。
另外,我們也可以通過子類對象訪問父類方法(在子類函數體中訪問父類方法,通過 $this
即可):
$benz = new Benz();
$benz->drive();
上述代碼的執行結果如下:
可以看到子類可以繼承父類所有通過 protected
和 public
聲明的屬性和方法,并且在調用過程中自動將 $this
指針引用指向子類對象,對于 public
屬性和方法,和父類一樣,直接可以在類外部通過 ->
操作符調用。
當然,你也可以在子類中新增一些獨有的屬性和方法:
class Benz extends Car
{
private $customProp = "自定義屬性";
...
public function customMethod()
{
echo "Call custom prop \$customProp: " . $this->customProp . PHP_EOL;
echo "This is a custom method in Benz Class" . PHP_EOL;
}
}
PHP 遵循單繼承機制,即一個子類只能繼承自一個父類。
2、封裝
概念解釋
封裝一方面指的是調用者無需關心對象方法實現細節,比如我們要開車,就調用 $car->drive()
方法即可,不用編寫具體的實現邏輯,也不用去關心(調用了那些屬性、那些方法、不管是私有的還是公開的、當前類的還是其他類的,統統不用關心),就像我們在真實世界中開車一樣,只需要按照流程來操作就好了,不用關心汽車引擎內部是如何工作的。
另一方面是通過訪問控制限定屬性和方法的可見性,比如 public
修飾的屬性和方法所有地方可見,不管是當前類、子類還是類之外,protected
修飾的屬性和方法在當前類和子類中可見,而 private
修飾的屬性和方法僅在當前類可見,你可以根據自己的業務需要合理的設置屬性和方法的可見性。
反射
不過,饒是如此,依然可以通過反射的方式將 protected
或者 private
級別的屬性和方法變成類以外可以訪問,比如我們將 Benz
類中的 customMethod
方法設置為私有的:
class Benz extends Car
{
private $customProp = '自定義屬性';
...
private function customMethod()
{
echo "Call custom prop \$customProp: " . $this->customProp . PHP_EOL;
echo "This is a custom method in Benz Class" . PHP_EOL;
}
}
在類外直接調用會報錯:
我們通過反射來調用這個方法,可以這么做:
// 通過反射調用非 public 方法
$method = new ReflectionMethod(Benz::class, 'customMethod');
$method->setAccessible(true);
$benz = new Benz();
$method->invoke($benz);
打印結果和調用聲明為 public
的 customMethod
方法完全一樣,如果將 private
修改為 protected
效果也一樣,通過反射,我們可以在運行時以逆向工程的方式對 PHP 類進行實例化,并對類中的屬性和方法進行動態調用,不管這些屬性和方法是否對外公開,所以這是一個黑科技,更多反射的細節可以參考 PHP 官方文檔:https://www.php.net/manual/zh/book.reflection.php。
3、多態
方法重寫
所謂多態,指的是在 PHP 繼承體系中,子類可以重寫父類的同名方法,這樣,在子類對象中調用該方法,就會自動轉發到子類方法調用,還是以 Car
和 Benz
為例,我們在子類中重寫父類的 drive
方法(所謂重寫,英文是 override,即在子類中編寫和父類同名方法,來覆蓋父類的實現):
class Benz extends Car
{
...
// 重寫父類實現
public function drive()
{
echo $this->getBrand() . '汽車的啟動流程:' . PHP_EOL;
parent::drive(); // TODO: Change the autogenerated stub
}
}
我們在子類的 drive
方法中,先打印了一段提示文本,然后和構造函數一樣,通過 parent::drive
調用父類的同名方法,因為所有的汽車啟動流程基本都是一樣的。
接下來,我們通過子類對象調用 drive
方法:
$benz = new Benz();
$benz->drive();
打印結果如下:
包含了第一行提示文本,所以,調用的是子類的方法而不是父類的。
類型轉化
PHP 不像 Java 那樣支持同一個類中定義多個同名方法(參數數量或類型不同,這種叫做方法重載),另外,由于子類一定包含了父類的公開方法,所以當類作為參數類型聲明時,如果聲明類型為父類,則可以傳入子類對象,反過來,如果聲明類型為子類,則不能傳入父類對象。
比如我們定義一個測試汽車類啟動功能的測試類和方法如下,并編寫一段測試代碼:
class TestCarDrive
{
public function testDrive(Car $car)
{
$car->drive();
}
public function testBenzDrive(Benz $benz)
{
$benz->drive();
}
}
// 初始化類對象
$bmw = new Car('寶馬');
$benz = new Benz();
$test = new TestCarDrive();
// 測試子類轉父類
$test->testDrive($benz);
// 測試父類轉子類
$test->testBenzDrive($bmw);
上述代碼第一個測試 $test->testDrive
可以正常運行,第二個會報錯: