組合
能用「有一個」描述就用組合,只有「是一個」才用繼承。
我對這句話的理解是,如果彼此之間沒有實際意義上的繼承關係和依賴關係,而只是為了程式碼複用,那麼直接採用組合是最好的方法。「是一個」本身指的就是有某種特徵的涵蓋,你需要滿足這個特徵,因此必須繼承。子類必須和父類在任何情況下「型別等同」,這就是里氏替換原則。但「有一個」卻沒有這種涵蓋在,因此不必要強用繼承,我只是為了借用某個類裡的能力就去繼承了,這種繼承就是沒必要的,反而會導致不必要的耦合。未來如果需要修改父類,那所有的子類都可能會牽連——你根本不知道哪個子類繼承了某個父類功能,然後現在被你改掉了。
具體來說,組合就是一種委託模式,我們之前說到DI,依賴注入正是組合的完美搭配。組合的委託正是「只關心結構」的體現,那就是我需要用到你的功能,但我不關心你具體怎麼實現,我只知道你有這樣的結構(或者可操作的方法與型別),我用了你的方法(必然規定輸入和輸出),用了你的型別,到此為止。你怎麼實現我根本不在意,這點我「委託」給了你——而我只接受一個外部DI進來的實現。
這種思想就是 控制反轉(IoC)的體現,那就是我對自己所依賴的一些東西不做關心,而交給外部來實現。我不需要在內部去完成它的具體是實現,而只關心我要用的部分——這是結構意義上的契約,我只對介面負責。因此也可以說,IoC就是面向介面程式設計最極致的體現,其抽象層都比物件導向還要高——物件可以是一個實現的具體類,但介面就只是結構,可以連具體實現都沒有。
這裡的極致體現在「控制反轉」的字面意義上,那就是各個實際的業務類本身完全不關心具體什麼時候實現或者用到什麼東西,這是DI容器負責的。它關心的只是自己的範圍,也只對自己負責,其他的全部不管——或者說,交給外部管理。就像一塊塊積木,積木不對自己要搭到哪裡負責,一堆樂高你可以搭成任何樣子。而哪個搭樂高的人就是DI容器。
軟體設計裡面的各種設計思想很明顯的都受到工業的影響——或者說,有共通的地方。標準化、可替換化、去耦合——都是在將具體的功能打包成零件,隨時可替換可組裝。應該說這是兩個不同領域的共同收斂,為了解決複雜的現實生產問題,無數人配合、彼此不認識,各種動態的業務場景、需求隨時變,對於一個穩健性的系統來說,模組化似乎是目前可行的最佳的方向。
這背後有一個更一般的原理——複雜系統要維持可控性,必須在某個粒度上實現資訊隱藏。零件不需要知道整機的設計,函式不需要知道整個系統的架構,細胞不需要知道整個生物體的意圖。複雜性只能通過分層遮蔽來管理,這幾乎是普遍規律。 by claude
我贊同這個看法,本質上來說,複雜系統下的各個主體正是隻對自己負責——或者說它只能對自己負責,因為任何人都沒有全局資訊。而如果我們參考社會的話,就會知道這是一個巢狀的模組化結構。人組成組織,無陣列織組成國家,無數國家組成人類社會——每一層都是一個模組,但這個模組在不同層的運作規律可能是截然不同的。但社會和軟體工程有一個最大的不通,那就是後者是自上而下設計的,而社會是湧現的——但是,也不盡然。有時候我們會說shi山程式碼,本質上就是說,沒有誰能真正的設計完美架構,一個持續執行的系統總會趨向於混沌,到最後它也會變成生長出來而非設計的產物。
恐怕連微軟都不可能說自己百分百了解自己那龐大到系統到底如何精密運作,他們做的只是有問題補問題,保證整體上的一致性——但誰知道底層是不是變成了靠bug執行,只是五百個bug鏈正好讓一切正常。這就是大型系統的複雜性——換言之,動態的場景會讓一切設計失效,因為你沒有全局資訊,也無法預知需求。所以沒有最好的設計,只有夠用的設計,降低出問題的機率,但保不齊還是會出問題——剩下的,等真出問題再說,補就是了。既然大家都會出錯,那我們就需要知道設計的重要性:設計了可能出錯,但至少是可修復、可定位的;而不設計下的出錯,到時候恐怕連問題在哪兒都找不到。