PHP繼承竟然也需要顯性基因?
原創(chuàng)網(wǎng)上經(jīng)常流傳出php是語言鄙視鏈***端的那個(gè),曾經(jīng)大學(xué)學(xué)java,畢設(shè)用java,剛出來培訓(xùn)用java的我,在最初工作的2、3年時(shí)對php的面向?qū)ο笠彩穷H有意見,總覺得【不倫不類】,更別提對js的看法了。但是這些觀點(diǎn)都在經(jīng)歷越來越多的項(xiàng)目之后逐漸的淡化,甚至改觀。這里面包含著自己對項(xiàng)目、技術(shù)有著更多的理解,同時(shí),在這些年里,Web環(huán)境、技術(shù)也在不停的更新。不過今天不是來聊這些東西的,對于以上的問題,我的觀點(diǎn)可以總結(jié)為:技術(shù)是工具、手段,不合適就升級、換,就這么簡單。
話歸原題。雖然寫php已經(jīng)是將近8年的功底了,但因?yàn)楣ぷ麝P(guān)系,經(jīng)常需要涉及前后端的各種代碼,容易精分,也總會記岔。最近發(fā)生的一件事情讓我覺得,或許寫下來能夠讓自己清醒一點(diǎn)。
在某一年寫某個(gè)模塊時(shí)用到了static成員,在實(shí)現(xiàn)子類的過程中發(fā)現(xiàn)他們也共享著父類這個(gè)成員的值,具體來說就是我在某個(gè)子類A中改變了那個(gè)成員值,在另外一個(gè)子類B使用的時(shí)候結(jié)果意外的得到了A覆蓋后的值。當(dāng)時(shí)以為,原來static成員是在從聲明的地方開始的整個(gè)類別樹中共享的。后來一直隱約記得這個(gè)結(jié)論,在平常的代碼里面更謹(jǐn)慎的使用static成員,除非確認(rèn)寫的類是個(gè)獨(dú)立的工具類,不然不輕易使用static。
直到有一天我的老大跟我商量升級我之前寫的一個(gè)BaseModel,他無意中問我:好像你不喜歡用static成員?我說沒有啊,因?yàn)榭紤]到BaseModel會被經(jīng)常繼承成各種Model,如果我在這里用了static的話,將來容易踩坑。他表示不理解,然后過來與我辯論。我很義正言辭的說明了因?yàn)閟tatic成員會被共享,如果要調(diào)用兩個(gè)不同的子類的時(shí)候,那個(gè)static成員的變量的值就會像一個(gè)全局變量一樣不可控。他不同意。于是本著科學(xué)的精神,我們寫下了一個(gè)簡短的代碼來驗(yàn)證:
- class A {
- protected static $var1 = null;
- public static function test(){
- echo get_called_class().' '.static::$var1.'<br/>';
- }
- }
- class B extends A {
- protected static $var1 = 'b';
- }
- class C extends A {
- protected static $var1 = 'c';
- }
- B::test();
- C::test();
很顯然,這次是我敗了。我期待的結(jié)果是c c,不過其實(shí)是b c。那么這樣看起來其實(shí)子類的static成員是只在子類這一層共享的。但是我總覺得不對勁,明明在寫B(tài)aseModel的時(shí)候我已經(jīng)又栽過跟頭了,為什么這個(gè)驗(yàn)證出來并不支持我那個(gè)時(shí)候遇到的問題呢?于是我發(fā)現(xiàn)我記岔了。年輕多好。后來想起來,原來我這里不用static的原因僅僅是因?yàn)樵O(shè)計(jì)需要。
我以為我錯(cuò)了。直到前幾天又寫了幾個(gè)父子類(不是BaseModel了),大膽的用上了static成員,結(jié)果是轟轟烈烈的在自測中又摔了一跤。怎么回事!然后我仔細(xì)留意了一下自己這次的用法,將上面的例子改了一下運(yùn)行:
- class A {
- protected static $var1 = null;
- protected static $var2 = null;
- public static function test(){
- if(!static::$var2){
- static::$var2 = static::$var1;
- }
- echo get_called_class().' '.static::$var2.'<br/>';
- }
- }
- class B extends A {
- protected static $var1 = 'b';
- }
- class C extends A {
- protected static $var1 = 'c';
- }
- B::test();
- C::test();
結(jié)果是
- B b
- C b
如果說上次的結(jié)論是對了,那么這次又怎么解釋?這里明明就是表示$var2是A,B,C共享的。$var1和$var2的差別這樣看起來僅僅是有聲明和沒聲明的區(qū)別。于是我又改成這樣:
- class A {
- protected static $var1 = null;
- protected static $var2 = null;
- public static function test(){
- if(!static::$var2){
- static::$var2 = static::$var1;
- }
- echo get_called_class().' '.static::$var2.'<br/>';
- }
- }
- class B extends A {
- protected static $var1 = 'b';
- protected static $var2 = null;
- }
- class C extends A {
- protected static $var1 = 'c';
- protected static $var2 = null;
- }
- B::test();
- C::test();
結(jié)果是
- B b
- C c
我當(dāng)時(shí)內(nèi)心是崩潰的。于是我上了Stack Overflow,發(fā)現(xiàn)栽坑的不止我一個(gè)。
只有顯式的聲明出來的static成員才會被視為是只從屬于子類的。
只有顯式的聲明出來的static成員才會被視為是只從屬于子類的。
只有顯式的聲明出來的static成員才會被視為是只從屬于子類的。
重要的事情說三遍!不過如果子類很多的話, 動態(tài)決定值的成員 每個(gè)都這樣去聲明,就從寫代碼這件事上失去了用static的意義。一個(gè)更好的方法是,把$var2變成一個(gè)數(shù)組,每個(gè)類要用的值放在$var[__CLASS__]里面使用。
不過不管怎么說,如非必要,還是盡量不用static成員繼承吧。
還有一個(gè)有點(diǎn)類似的“坑”。我們說到private成員的時(shí)候,都知道private是指私有的,不會被子類繼承。但是有時(shí)候?qū)懘a的時(shí)候會忘記,直到載跟頭了才想起來原來是private導(dǎo)致子類找不到該有的成員,或者說是private都在子類聲明了,但是因?yàn)檎{(diào)用函數(shù)時(shí)是調(diào)用父類函數(shù),結(jié)果得到的是父類這個(gè)private的值而不是子類的。遇到這種情況不可能又將函數(shù)原樣的重寫在子類里。所以使用private要特別小心。
曾經(jīng)在使用Rackspace的SDK的時(shí)候就看到有些類里面使用了private成員,但是由于他們給出了不必要的打開文件權(quán)限,導(dǎo)致代碼在我們的服務(wù)器上運(yùn)行不了。那么這個(gè)時(shí)候本想寫個(gè)子類覆蓋一下這個(gè)成員的初始值就好了,結(jié)果就因?yàn)檫@是個(gè)private成員,而***需要把所有引用到的地方都拷到自己寫的子類里面。為什么我們不直接改SDK,讓成員變成protected?因?yàn)殚_發(fā)包也許下次就升級了呢?修正之后我們把子類移除就好了。如果修改庫代碼成了習(xí)慣,想升級的時(shí)候就沒這么歡了。所以說,private成員的使用一定要慎之又慎,如果你也在開發(fā)SDK,就更需要考慮使用者是不是需要繼承?如果你必須寫private,你是不是能夠保證代碼能夠適應(yīng)各種場景的使用?
除非你有非常充分的理由,static和private都是需要慎重使用的。