很多Java程序員喜歡將“面向?qū)ο笕筇匦浴狈顬楣玺叶福貏e是“繼承”,在他們眼中仪壮,“繼承”是“面向?qū)ο蟆弊钪匾奶匦院┑撸聦嵣蠌V義的“面向?qū)ο蟆辈]有限定必需“繼承”,甚至“面向?qū)ο蟆边@個概念在業(yè)界上也還沒達成統(tǒng)一积锅,比如像Go和Rust這類不支持“繼承”的編程語言爽彤,也有很多人認為它們是“面向?qū)ο蟆钡恼Z言。
今天我就想給很多Java程序員們潑一盤冷水:“類繼承”是有害的乏沸。
為什么我明確是“類繼承”呢淫茵?因為“接口繼承”并不在此列,所以需要特別指出蹬跃。
我的觀點是:在所有的大型Java項目中,凡是最難以維護铆铆、難以修改蝶缀、難以閱讀、牽一發(fā)而動全身的代碼薄货,必然是那些使用了“類繼承”的代碼翁都。
這是因為“類繼承”違反了一個設(shè)計原則:最小職責(zé)原則。
對于父類來說谅猾,它即是實現(xiàn)又是抽象柄慰。實現(xiàn)是對于父類本身而言,它可以被實例化成對象税娜;而抽象則是對于子類來說坐搔,它是子類的抽象。這兩個特性分開來都說好特性敬矩,組合在一起就變成了難吃的五仁月餅概行。
對于實現(xiàn)而言,父類就不可能做得太小弧岳,因為它是有功能性的凳忙,即使它把單一的功能拆散成多個類再組合起來,這樣對于子類來說禽炬,也是繼承了父類的全部步淹,而這恰恰造成了很大的耦合度——要是在將來重構(gòu)發(fā)現(xiàn)子類不需要某個方法,也無法去掉健民,因為一旦去掉憾朴,就不滿足父類了。你可能覺得可以在方法體里拋個異常即可,但是對于調(diào)用方來說胎撤,一旦不知道這一點晓殊,就會出現(xiàn)BUG,一旦系統(tǒng)中這種設(shè)計越來越多伤提,整個系統(tǒng)就會混亂不堪巫俺,就像縫縫補補的破褲子一樣。
而對于抽象來說肿男,父類承擔(dān)起了接口的責(zé)任介汹,一個巨大的接口,這也是Java程序員津津樂道的“多態(tài)”舶沛。但是可怕的是嘹承,父類的多態(tài)類,有可能是它繼承樹下面的所有子類中的其中一個如庭,而這個子類叹卷,又可能是其他子類的父類,你需要了解整個繼承鏈路上的所有子類坪它;而對于接口而言骤竹,實現(xiàn)類就是一張鋪平的網(wǎng),你要了解的這里的其中一個即可往毡。
經(jīng)驗豐富的Java程序員有一種說法:“類繼承不能夠超過三層蒙揣,否則就會造成混亂”。這是根據(jù)經(jīng)驗而得出的結(jié)論开瞭,而對于“接口繼承”來說懒震,卻沒有這種說法,一方面是接口很少情況會需要這么多層的繼承嗤详,另一方面是即使超過100層的接口繼承个扰,也不會造成混亂。這是為什么断楷?
因為“類繼承”的混亂是指數(shù)級別的增長锨匆,而“接口繼承”則是線性增長。對于混亂程度冬筒,物理學(xué)上把這種程度定義成熵恐锣,熵的符號是S,假設(shè)繼承層數(shù)是N舞痰,那么土榴,“類繼承”的熵就是,而“接口繼承”則是响牛。
在多層繼承里玷禽,方法的覆蓋會非澈斩危混亂,你不知道方法到底在哪里被覆蓋了矢赁,繼承鏈路上所有子類都有可能是罪魁禍首糯笙。而對于接口來說,即使是接口中的方法是默認方法撩银,也沒關(guān)系给涕,因為只有實現(xiàn)類最終會調(diào)用到方法,更重要的是额获,接口不會被實例化够庙,因此它可以包含最少的方法,就是所謂的單一職責(zé)原則抄邀,而讓實現(xiàn)類實現(xiàn)多個接口耘眨。這樣接口就沒有必要去繼承了。
隨著組合由于繼承的觀念深入人心境肾,很多主流的框架和組件都放棄了繼承剔难,比如Spring,你基本上不需要用到繼承奥喻,除非你自作主張钥飞。
在Java8之前,“類繼承”尚且有他存在的意義衫嵌,那就是代碼復(fù)用;而在Java8之后彻秆,由于接口有了默認方法這一特性楔绞,“類繼承”就變成了徹頭徹尾的垃圾——抱歉我用“垃圾”兩字來形容它,因為我真的不想看到那些使用了繼承還沾沾自喜的代碼了唇兑。