JavaRush /Java Blog /Random-TW /繼承作為一種現象
articles
等級 15

繼承作為一種現象

在 Random-TW 群組發布
說實話,我原本並沒有打算寫這篇文章。我認為我在這裡要討論的問題是微不足道的,甚至不值一提。然而,在為本網站撰寫文章的過程中,我在一個論壇上提出了關於多重繼承的討論。結果發現,大多數開發者對於繼承的理解非常模糊。因此,他犯了很多錯誤。由於繼承是 OOP 最重要的特性之一(如果不是最重要的話!),因此我決定專門寫一篇文章來討論這一現象。* * * 首先,我想區分兩個概念—物件和類別。這些概念經常被混淆。同時,它們是 OOP 的核心。而且,在我看來,有必要了解它們之間的差異。那麼,對象。基本上,什麼都可以。這是立方體。木質、藍色。邊緣的長度是5厘米,這是一個物體。還有一座金字塔。塑膠, 紅色. 10 公分肋骨。這也是一個物件。他們有什麼共同點?不同的尺寸。形狀不同。材質不同。然而,他們有一些共同點。首先,正方體和金字塔都是正多面體。那些。頂點數和麵數和比邊數多 2。更遠。兩種形狀都有面、邊和頂點。兩個圖形都具有肋骨尺寸等特徵。兩種形狀都可以旋轉。兩個圖都可以畫出來。最後兩個屬性已經是行為。等等。程式設計實踐表明,操作同類物件比操作異質物件容易得多。由於這些人物之間仍然存在一些共同點,因此希望以某種方式強調這種共同點。這就是階級概念發揮作用的地方。那麼,定義。
類別是一組物件的公共屬性的描述符。這些屬性既可以是物件的特徵(大小、重量、顏色等),也可以是行為、角色等。
評論。沒有說出“全部”(所有屬性的描述符)這個詞。這意味著任何物件都可以屬於多個不同的類別。 繼承作為一種現象 - 1 讓我們以幾何形狀為基礎來舉出同樣的例子。最一般的描述是正多面體。無論邊緣大小、面數和頂點數如何。我們唯一知道的是這個圖形有頂點、邊和麵,邊的長度相等。 更遠。我們可以讓描述更具體。假設我們要繪製這個 多面體讓我們引入一個繪製正多面體這樣的概念。畫畫我們需要什麼?描述不依賴特定頂點座標的通用繪製方法。也許是物體的顏色。 現在我們來介紹CubeTetrahedron類別。屬於這些類別的物體當然是正多面體。唯一的差異是每個新類別的頂點、邊和麵的數量已經嚴格固定。進一步地,知道了具體圖形的類型,我們就可以給出繪製方法的描述。這意味著 CubeTetrahedron類別的任何物件也是繪製的 正多面體類別的物件。存在類別的層次結構。在這個層次結構中,我們從最一般的描述下降到最具體的描述。請注意,任何類別的物件也符合層次結構中任何更通用類別的描述。這種類別關係稱為 繼承。每個子類別都繼承父類別的所有屬性,更一般地說,並且(可能)將一些自己的屬性添加到這些屬性中。或者它重寫了父類別的一些屬性。這裡我想引用Gradi Bucha關於物件導向設計的經典著作:
因此,繼承定義了類別之間的「是一個」層次結構,其中子類別繼承自一個或多個超類別。這其實是遺傳的試金石。給定類 A 和 B,如果 A「不是」B 的一種,那麼 A 不應該是 B 的子類。
翻譯一下聽起來是這樣的:
因此,繼承定義了類別之間的「is」層次結構,其中子類別繼承自一個或多個超類別。事實上,這是繼承的定義測試(字面意思是試金石,我的註釋)。如果我們有類 A 和 B,而類 A「不是」類 B 的變體,那麼 A 一定不是 B 的子類。
讀到這裡的人可能會困惑地用手指轉動太陽穴。第一個想法是,這只是微不足道的事情!這是真實的。但如果你知道我看過多少瘋狂的繼承層次結構!在我一開始提到的那個討論中,其中一位參與者非常認真地從…機槍繼承了一輛坦克!原因很簡單,坦克車有一挺機槍。這是最常見的錯誤。繼承與聚合(將一個物件包含在另一個物件中)混淆。坦克不是機關槍,它裝有一挺機槍。由於這個錯誤,大多數情況下人們希望使用多重繼承。現在讓我們直接轉向 Java。繼承方面有什麼?該語言中有兩種類型的類別 - 能夠包含實現的類別和不能包含實現的類別。後者稱為接口,儘管本質上它們是完全抽象的類別。 繼承作為一種現象 - 2 因此,該語言允許您從可能包含實作的另一個類別繼承一個類別。但僅來自一個!讓我解釋一下為什麼要這樣做。關鍵在於每個實作只能處理它自己的部分——它知道的那些變數和方法。即使我們從 AB繼承類別 C,那麼從類別 A 繼承的方法 processA也只能使用內部變數 a ,因為它對 b一無所知,就像它對 c和方法 processC一無所知一樣。 processB方法也是如此 只能與變數 b 一起使用。也就是說,本質上,繼承的部分是孤立的。C 類別當然可以與它們一起工作,但如果它們只是作為 C 類別的一部分包含在內而不是繼承的話,它也可以與這些部分一起工作。然而,這裡還有另一個麻煩,那就是名稱的重疊。 如果processAprocessB的方法命名相同,即 process,那麼呼叫類別 C的 process方法會產生什麼效果?將呼叫這兩個方法中的哪一個?當然,C++ 在這種情況下有控制權,但這並不能增加語言的和諧性。因此,實作繼承沒有優點,但也有缺點。因此,Java 中的這種實作繼承已被放棄。但是,開發人員可以選擇多重繼承,例如從介面繼承。用 Java 術語來說,就是介面實作。接口是什麼?一套方法。(我們目前不考慮介面中常數的定義;更多資訊請參見 此處。)什麼是方法?方法的核心決定了物件的行為。幾乎每個方法的名稱都包含一個操作,這並非巧合 - getXXXdrawXXXcountXXX等。由於介面是方法的集合,因此 介面實際上是 行為的 決定因素。使用介面的另一個選擇是定義物件的角色。 觀察者、傾聽者等 在這種情況下,該方法實際上是對某些外部事件的反應的體現。這又是行為。一個物件當然可以有幾種不同的行為。如果需要渲染,就渲染。如果他需要被拯救,他就被拯救了。嗯,等等。因此,從定義行為的類別繼承的能力非常非常有用。同樣,一個物件可以有多個不同的角色。然而, 實施 行為完全取決於子班的良心。從介面(其實作)繼承表明此類的物件應該能夠執行此操作。它如何做到這一點取決於每個獨立實作介面的類別。讓我們回到繼承中的錯誤。我開發各種系統的經驗表明,透過介面繼承,您可以實現任何系統,而無需使用多個實作繼承。因此,當我遇到關於 C++ 中缺乏多重繼承的抱怨時,對我來說,這肯定是設計不正確的跡象。最常見的錯誤就是我已經提過的錯誤——繼承與聚合混淆。有時,這種情況的發生是由於錯誤的假設。那些。以車速表為例,有人認為只能透過測量距離和時間來測量速度,之後車速表成功地繼承了尺和鐘錶,從而成為了尺和鐘錶,根據遺產。(我要求用速度計測量時間,通常他們都會用笑話來回答。或者他們根本不回答。)這裡有什麼錯誤?在前提下。事實上,車速表不測量時間。順便說一句,還有距離。任何速度計中都存在的里程表是同一外殼中第二個設備的典型範例,即 聚合。不需要測量速度。它可以完全刪除 - 這不會以任何方式影響速度測量。有時這樣的錯誤是故意犯的。這更糟。“是的,我知道這樣不對,但這對我來說更方便。” 這會導致什麼?但事情是這樣的:我們將從大砲和機槍繼承坦克車。這樣比較方便。結果,坦克就變成了大砲和機關槍。接下來我們將為飛機配備兩挺機槍和一門大砲。我們得到什麼?一架懸掛著三輛坦克武器的飛機!因為肯定有人在不理解的情況下將坦克用作機槍。完全根據繼承層次結構。他是絕對正確的,因為設計這樣一個等級制度的人犯了一個錯誤。
總的來說,我不太理解「這樣對我來說更方便」的做法。像聽者一樣寫起來很方便,說基本文法的都是kazly。當然,我有些誇張,但主要想法仍然是──除了一時的便利之外,還有讀寫能力這樣的東西。這個概念是根據大量人的經驗來定義的。事實上,這就是英語中所說的「最佳實踐」——最佳解決方案。通常,看似簡單的解決方案在未來會帶來許多問題。
當然,這個例子是非常誇張的,因此是荒謬的。然而,也有一些不太明顯的案例卻導致了災難性的後果。透過從物件繼承而不是聚合它,開發人員使任何人都能夠直接使用父物件的功能。就其所暗示的一切而言。想像一下,您有一個與資料庫一起使用的類別 DBManager您可以使用DBManager - DataManager建立另一個類別來處理您的資料。此類別將執行資料控制、轉換、附加操作等。一般來說,是業務層和基礎層之間的一層。如果從 DBManager 繼承 DataManager,那麼使用它的任何人都可以直接存取資料庫。因此,他將能夠繞過控制、轉換等執行任何行動。好吧,我們假設沒有人想要造成故意傷害,直接行動就可以了。但!我們假設基礎已經改變。我的意思是,一些控製或轉換的原則已經改變。資料管理器已更改。但之前直接使用資料庫的程式碼將繼續運作。他們很可能不會記得他。結果將是這樣一個類別的錯誤,那些尋找它的人會變成灰色。任何人都不會想到他們會繞過 DataManager 使用資料庫。順便說一下,一個現實生活中的例子。花了很長時間才發現錯誤。最後,我再說一次。 僅當存在「是」關係時才應使用繼承。因為這是繼承的本質-使用子類別的物件作為基底類別的物件的能力。如果類別之間沒有「是」關係,就不應該有繼承!從來沒有並且在任何情況下都不會。更重要的是——因為它太方便了。原始來源連結:http://www.skipy.ru/philosophy/inheritance.html
留言
TO VIEW ALL COMMENTS OR TO MAKE A COMMENT,
GO TO FULL VERSION