細(xì)說 PHP 7.2 子類覆蓋方法省略參數(shù)類型功能以及 Liskov 替換原則
PHP 7.2 出來(lái)也有段時(shí)間了,關(guān)于新版本有什么新改進(jìn),只要你關(guān)心 PHP 的發(fā)展,應(yīng)該都看過。這里只細(xì)說一個(gè)可能會(huì)有誤解的新功能。
PHP 7.2 可以在當(dāng)子類覆蓋(override)父類方法的時(shí)候,忽略父類方法的定義的參數(shù)的類型(type hint):
class Foo { public function bar(SomeClass $obj) {} } class Foobar extends Foo { public function bar($obj) {} // 這在 PHP7.2 版本之前是會(huì)報(bào)錯(cuò)的 }
我看有些網(wǎng)站介紹此功能的時(shí)候,說其目的是為了『方便重構(gòu)。如果以后父類方法的參數(shù)類型變了,子類不用再全部換一遍』。聽起來(lái)好像很有道理。按這說法,隱含的意思是:如果子類忽略了父類方法參數(shù)類型,被調(diào)用時(shí)還是會(huì)檢查參數(shù)類型。實(shí)際情況是不是這樣做一下實(shí)驗(yàn)就知道了:
<?php class Foo { } class Bar { public function setFoo(Foo $foo) { } } class BarKid extends Bar { public function setFoo($foo) { } } $kid = new BarKid; $kid->setFoo('I am a string!');
如果上面的說法是對(duì)的,setFoo 接受字符串參數(shù)的時(shí)候就應(yīng)該報(bào)錯(cuò),然而上面代碼在 7.2 下并沒有任何報(bào)錯(cuò)信息,但如果子類的 setFoo 方法加上了參數(shù)類型,就會(huì)立馬報(bào)錯(cuò)了。記住網(wǎng)上很多說法都不可信,除了我這個(gè)小站……
上面的實(shí)驗(yàn)說明子類方法可省略參數(shù)類型,其目的肯定不是為了方便重構(gòu)。那真正目的是什么呢?
在 PHP 7.1 里有一個(gè)新功能,是『可設(shè)置方法或函數(shù)的參數(shù)和返回類型是否可以為 null』。其中有一條看上去比較別扭的規(guī)則:『子類方法參數(shù)類型范圍放寬(即父類參數(shù)若不能為 null ,子類參數(shù)可支持 null),但返回類型縮緊(父類若不能返回 null,子類必須也不行;若父類可以返回 null,子類可以不返回 null)』,當(dāng)時(shí)我很簡(jiǎn)單說了一句,是因?yàn)?『Liskov 替換原則』,但沒有做深入介紹。身邊的 PHPer 們關(guān)注 OOP 原則的不多,但我認(rèn)為它應(yīng)該被每個(gè)工程師知道,還是介紹一下。
Liskov 替換原則簡(jiǎn)單一句話:父類出現(xiàn)的地方,替換成子類也能運(yùn)行,即子類可無(wú)腦替換父類。其實(shí)從語(yǔ)言設(shè)計(jì)來(lái)說,我認(rèn)為此原則就是對(duì)自然規(guī)則的模仿2018-09-29 補(bǔ)充:也不是簡(jiǎn)單的『模仿』,有興趣可閱讀新博客『企鵝不是鳥』。
舉個(gè)例子,人可以喝酒,喝茶,喝可樂,喝各種飲料,但人作為哺乳動(dòng)物,怎么著都能喝水吧?但反過來(lái),哺乳動(dòng)物能喝水,但不一定能喝酒喝茶喝可樂,所以人是哺乳動(dòng)物的子類。
從語(yǔ)言設(shè)計(jì)的角度來(lái)說,子類就應(yīng)該是父類的加強(qiáng)版,就是要能比父類處理