Charles Scalfanijava
原文: https://medium.com/@cscalfani...
我使用面嚮對象語言編程已經十幾年了。我是用的第一個OO語言是C++,而後是Smalltak,最後是.NET和Java。程序員
我迫切地想從面向對象的三大支柱,集成,封裝和多態上獲得收益。編程
我急於從這個到我面前的新領地獲得對於重用的承諾。微信
我對於將現實對象映射到類的想法很是興奮,並但願整件事能平滑遷移。ide
我想太多了。測試
最先,繼承看起來是面向對象範式的最大收益。全部對新手灌輸的關於形狀繼承的簡化例子看起來邏輯上很合理。this
我照單全收而且發現了新東西。spa
我帶着信仰和須要解決的問題,開始構建類繼承和寫代碼,一切都很好。.net
我永遠不會忘記那一天,當我打算開始從一個現有類使用繼承來重用的時候,這是我一直在等待的時刻。翻譯
一個新項目來了,我想到在我上一個工程裏的那個類。
沒問題,重用來搞定。我從老工程裏找到那個類並拷過來使用。
可是。。。 不僅是那個類。我須要父類。但。。 但先這樣。
啊。。。等下。。。看起來咱們須要這個父類的父類。。。而後。。。 咱們須要全部父類。好吧。。好吧。。我來解決這個。沒問題。
我去。如今不能編譯。爲何?哦,我知道了。。。 這個對象包含了其餘對象。因此我也須要那些。 沒問題。
等等。。。我不僅是須要那個對象。我須要對象的父類和他父類的父類,而後每一個包含的對象和他們全部的父類。。。
暈。
Joe Armstrong,Erlang之父曾說過:
面嚮對象語言的問題是他們隱式的包含了他們周圍的環境。你須要一個香蕉可是你獲得的是一個拿着香蕉的大猩猩和整個雨林。
我能夠經過不寫太深的繼承來解決這個問題。但複用的關鍵就是繼承,任何我對這個機制上的限制都直接限制了重用,是吧?
是的。
因此可憐的面向對象程序員,who’s had a healthy helping of the Kool-aid, to do?
組合和委託,後面說這個。
如下問題早晚會遇到,取決於使用的語言。
大部分OO語言不支持這個,儘管這個看起來符合邏輯。讓OO語言支持這個有多難?
想象下如下僞代碼:
Class PoweredDevice { } Class Scanner inherits from PoweredDevice { function start() { } } Class Printer inherits from PoweredDevice { function start() { } } Class Copier inherits from Scanner, Printer { }
注意Scanner類和Printer類都實現了一個start功能。
因此Copier累繼承了哪個start功能?Scanner?仍是Printer?不可能兩個都實現。
方案很簡單。不要這麼作。
是的。大部分OO語言不讓你這麼作。
可是,若是個人建模就是這樣呢?我須要個人重用!
那麼你必須使用組合和委託。
Class PoweredDevice { } Class Scanner inherits from PoweredDevice { function start() { } } Class Printer inherits from PoweredDevice { function start() { } } Class Copier { Scanner scanner Printer printer function start() { printer.start() } }
注意如今Copier類包含了Printer和Scanner的實例。他將start功能委託給Printer類的實現。他也能夠簡單委託給Scanner。
這個問題也讓繼承範式開始出現問題。
因此如今我保證個人繼承關係比較扁平,並不會出現環狀引用。沒有鑽石問題。
如今一切正常,直到。。。
一天,個人代碼運行正常,但後一天就不工做了。我沒有變動個人代碼。
那麼,這多是個bug。。。 但等下。。。 有些東西確實變了。。。
但那個變更不在個人代碼裏。這個變更是在我繼承的類裏面。
爲何基類的變更會致使個人代碼有問題?
咱們先設想有個基類(我用Java寫的,你不懂Java應該也能夠比較容易的理解):
import java.util.ArrayList; public class Array { private ArrayList<Object> a = new ArrayList<Object>(); public void add(Object element) { a.add(element); } public void addAll(Object elements[]) { for (int i = 0; i < elements.length; ++i) *a.add(elements[i]); // this line is going to be changed* } }
重要:注意註釋的那段代碼。這段代碼後面的變動會破壞邏輯。
這個類的接口有兩個功能, add()和addAll()。add()會加一個單獨的元素, addAll()會調用add方法來增長多個元素。
這是衍生類:
public class ArrayCount extends Array { private int count = 0; @Override public void add(Object element) { super.add(element); ++count; } @Override public void addAll(Object elements[]) { super.addAll(elements); count += elements.length; } }
ArrayCount類是Array類的一個具體實現。惟一的行爲區別是ArrayCount保存了元素的數量(count)。
讓咱們看下兩個類的細節。
Array add()添加一個元素到本地的ArrayList。
Array addAll()爲每一個元素循環調用本地的ArrayList。
ArrayCount add()調用父類的add()而且增長數量count。
ArrayCount addAll()調用父類的addAll()而後根據元素的數量增長數量count。
目前看起來都正常。
如今打破邏輯了。基類註釋的代碼變動成如下這樣:
public void addAll(Object elements[]) { for (int i = 0; i < elements.length; ++i) add(elements[i]); // this line was changed }
基類全部者關心的部分,功能仍是按設想同樣運轉正常。而且全部自動化測試仍然能夠經過。
但全部者顯然沒有關注到派生類。因此派生類的做者被粗暴干擾了。
如今ArrayCount addAll()調用父類的addAll(),其內部調用add()的邏輯已經被派生類覆蓋了。
這樣會致使數量count在每次派生類調用add()時增長,而後在派生類調用addAll()時再被增長一次。
這被計數了兩次。
若是是這樣,而且已經發生了,派生類的做者必須知道積累是被如何實現的。他們必須在每次基類變動時被通知到,由於這可能會致使派生類在不可預見的狀況下工做。
太糟了!這個巨大的問題永久影響了繼承範式的穩定性。
此次同樣,包含和委託能夠解決。
使用包含和委託,咱們從白盒編程轉化成黑盒編程。白盒編程時,咱們須要關注基類的實現。
黑盒編程時,因爲咱們沒法經過覆蓋基類方法的方式來注入代碼,咱們能夠徹底忽略其實現。咱們只須要關心接口。
這個趨勢有點危險。。。
繼承應該是重用最重要的手段。
OO語言沒有設計成讓包含和委託方便使用。他們是被設計成讓繼承方便易用。
若是你像我同樣,你會開始對這個繼承的問題開始驚奇。但更重要的是,這會讓你對於繼承的信心開始動搖。
每次當我進入一家新公司,我都會對於找個地方放我公司文檔的地方開始糾結,好比,員工手冊。
我是建一個目錄叫「文檔」而後在裏面建個目錄叫「公司」?
或者我建一個目錄叫「公司」而後在裏面建個目錄叫「文檔」?
均可以。可是哪個是正確的? 是最好的?
目錄繼承的想法是基類(父母)更加通用,派生類(子類)會更加具體。並且咱們本身會在繼承鏈上作更加具象化的版本。(看上面形狀繼承的例子)
但當一個父類和子類能夠互相調換位置時,這個模型明顯哪裏出了問題。
如今的問題是。。。
分類繼承不工做了。
因此繼承方式好在哪裏?
包含。
若是你看下現實世界,你能夠看到包含(或排他全部權)繼承處處都是。
而你找不到的是分類繼承。讓那個先等一會。面向對象範式來源於於現實世界,對象被另外一個對象填入。但他使用了一個有問題的模型。分類繼承,沒有現實世界的基礎。
現實世界使用的是包含繼承。一個容器包含繼承的很好的例子是你的襪子。他們在襪子的抽屜裏,而後被你衣服的抽屜包進去,而後又被你的臥室包含,而後又被你的房子包含。
你硬盤的目錄是另外一個容器包含繼承的例子。他們保存文件。
因此咱們如何對他們分類?
若是你考慮下公司目錄,其實我放在哪裏沒什麼太大關係。我能夠把他們放在一個叫「文檔」的目錄或放在一個叫「東西」的目錄。
我分類的方式是使用tag標籤。我使用如下標籤來給文件打標:
文檔 公司 手冊
標籤沒有順序或繼承。(這也解決了鑽石問題)
tag與接口相似,你能夠有多種類型與文檔關聯。
看到這麼多問題,看起來繼承範式已經完了。
再見,繼承。
微信公衆號「麥芽麪包」,id「darkjune_think」開發者/科幻愛好者/硬核主機玩家/業餘翻譯家/書蟲交流Email: zhukunrong@yeah.net