2012-11-16 30 views
38

我知道在StackOverflow中有幾個類似的問題,如this question爲什麼重寫方法參數違反了PHP中的嚴格標準?

爲什麼重寫方法參數違反了PHP中的嚴格標準? 例如:

class Foo 
{ 
    public function bar(Array $bar){} 
} 

class Baz extends Foo 
{ 
    public function bar($bar) {} 
} 

嚴格的標準:巴茲::巴的聲明()應該與 兼容是富::酒吧()

在其他OOP編程語言,你可以。爲什麼PHP不好?

回答

46

在OOP,SOLID代表單一職責,開閉,Liskov置換,界面分離和依賴倒置

Liskov substitution原理指出的是,在一個計算機程序中,如果酒吧一個亞型的Foo,然後Foo類型的對象可以與酒吧類型的對象替換而不改變任何所需性能的該程序(正確性,執行的任務等)。

在強類型編程語言中,當覆蓋Foo方法時,如果更改Bar中的簽名,實際上將重載,因爲原始方法和新方法可用於不同的簽名。由於PHP是弱類型的,這是不可能實現的,因爲編譯器無法知道你實際調用了哪些方法。 (因此,即使它們的簽名不同,也不能有兩個同名的方法)。

因此,爲了避免違反Liskov替代原則,會發出一個嚴格的標準警告,告訴程序員可能會由於子類中方法簽名的更改而中斷某些事情。

+20

我不同意這裏的兩點:「如果Bar是Foo的一個子類型,那麼Foo類型的對象可能被Bar類型的對象替代(反之亦然)」。反之則不成立;你不希望在任何使用sublcass的地方使用超類。另一件事是關於LSP:如果參數是逆變的,那麼類型是受尊重的。如果PHP是純粹的OO,那麼就沒有問題了,因爲Array比Object更窄(假設聲明中沒有任何類型意味着任何對象)。因此bar()的實現在Baz中更加通用,使它適合LSP –

+1

@AndrésFortier反之亦然,我的意思是在Foo的範圍內,Foo和Bar應該是可以互換的。然而關於第二個話題,我同意你的看法。 Kaern給出的例子不違反LSP,因爲Baz :: bar()可以安全地替代Foo :: bar()。 – Tivie

+2

非常好的解釋,謝謝。那麼爲什麼我可以在構造函數中打破Liskov原理呢? –

5

您可以重寫參數,但簽名應該匹配。如果您已將Array置於$bar之前,則不會出現問題。

例如,如果您添加了附加參數,則不會有任何問題,只要第一個參數具有相同類型的提示即可。這是任何語言的良好習慣。

+0

但爲什麼要簽名匹配?在Java中,我可以更改簽名 –

+1

@KaernStone,您也可以在PHP中使用,但不應該這樣做。假設你的'Foo'的子類叫做'Baz',而其他一些代碼想要與'Baz'的一個實例進行交互,就好像它是'Foo'。它應該能夠在沒有修改的情況下做到這一點,因爲'Baz' **是**'Foo'。 PHP將允許你打破這個原則,但你不應該這樣做,除非你有一個很好的理由這樣做。 – Brad

+1

@布拉德,請參閱我在蒂維的回答中的評論。如果不在參數中加入一個類型意味着該方法可以接受任何對象,這意味着它們是逆變的,因此Baz **是-a ** Foo。從打字系統的角度來看,應該沒有問題。請參見[這裏](http://en.wikipedia.org/wiki/Covariance_and_contravariance_%28computer_science%29) –

2

因爲你Foo宣佈$bar應該array型的,而在擴展Bar$bar的類型不聲明。

這不是一個錯誤,它是一個警告。您應該使方法定義與原始基類兼容。你可以,但是,忽略它,如果你知道你在做什麼(僅如果你知道你在做什麼!)

+0

是的,我知道。但爲什麼我應該使方法定義與原始基類兼容? –

+1

@KaernStone:因爲其他類/方法/應用程序依賴於原始界面,並且如果您更改它,您可能會破壞兼容性。 –

16

我知道我遲到了,但答案並沒有真正解決實際問題。

問題是PHP不支持函數/方法重載。以無類型語言支持函數重載將會很困難。

暗示有幫助。但在PHP中是非常有限的。不知道爲什麼。例如,你不能提示一個變量是一個int或布爾值,但數組很好。去搞清楚!

其他面向對象的語言使用函數重載來實現這一點。也就是說功能的簽名明顯不同。

因此,舉例來說,如果下面是可能的,我們不會有問題

class Foo 
{ 
    public function bar(Array $bar){ 
     echo "Foo::bar"; 
    } 
} 

class Baz extends Foo 
{ 
    public function bar(int $bar) { 
     echo "Baz::bar"; 
    } 
} 


$foo = new Baz(); 
$bar = new Baz(); 
$ar = array(); 
$i = 100; 

$foo->bar($ar); 
$bar->bar((int)$i); 

would output 

Foo::bar 
Baz::bar 

當然,當它來構造的PHP開發人員意識到他們必須實現它,喜歡還是不喜歡!所以他們只是在第一種情況下抑制錯誤或不提高它。

這是愚蠢的。

一個熟人曾經說過PHP實現的對象只爲實現命名空間的一種方式。現在我不太那麼批評,但是一些做出的決定傾向於支持這一理論。

我總是有最大的警告變成開發代碼的時候,我從來沒有讓一個警告,去了不理解這意味着什麼,什麼樣的影響是。我個人不關心這個警告。我知道我想要做什麼,PHP並沒有做到這一點。我來到這裏尋找一種方法來選擇性地抑制它。我還沒有找到方法。

所以我會陷入這個警告並且自己壓制它。羞愧我需要這樣做。但我對STRICT嚴格要求。

+2

關於構造函數,在PHP 5.4+中,方法簽名在擴展_abstract_基類時必須兼容,否則會出現致命錯誤。有點奇怪,在PHP 5.4之前甚至沒有E_STRICT警告,並且如果基類不是抽象的,那麼不管PHP版本是什麼都沒有消息。 – MrWhite

+0

謝謝謝謝謝謝。這是我見過的第一個也是唯一的地方,它旨在壓制構造函數中的錯誤,這是一個非常討厭的問題。這是對LSP的違反,因爲允許構造函數中的子類型參數是加強前提條件的一種情況。方法論點應該永遠是不變的或逆變的,而不是協變的。事實上,在PHP中,我認爲這是唯一的情況,除了重寫方法中的類型化參數的不變性之外,其他任何情況都是可能的。 –

相關問題