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