繼承是一個非常有用的概念,但是卻很容易被用得不合適,通常用接口來實現可能會更好,本文的目的就是使用戶懂得怎樣更好的使用類的繼承。
當遇到如下情況時,繼承將是一個好的選擇:
(1)簡化一個低等級的不使用類的API函數
(2)從基本類中得到重用的代碼
(3)需要對不同的數據類型使用相同名稱的類和方法
(4)類的層次相當,最多4到5級,而且不僅增加一級或兩級
(5)通過改變一個基本類,就改變所有的派生類。
1.簡化一個低等級的不使用類的API函數
類庫使得一系列的函數的調用更加合理,特別是當調用的這些函數不是能類的形式組織的時候,舉一個例子,就象那些為簡化在窗體中畫圖表的API函數而設計的類一樣,這種類庫在有些時候被叫做"Wrapper"。這些把API函數包括起來的類庫通常可以簡化操作,不過這樣做並沒有企圖把API函數的一些基本特性給隱藏掉。Windows Graphics API函數有很多,但用戶並不需要了解它底層的一些變化。用戶在設計類的時候也是這樣,必須考慮類庫的用戶對底層的了解程序。實際上,很多類庫都是根據API函數來組織的,通常它們有兩個趨勢:使得API函數對高級用戶更加方便,或者通過高度的概括,把這些底層的信息隱含起來,這意味著把Windows API函數的具體信息對用戶封裝。
但是這裡又有一個隱含的關於開發類庫的問題,被打成包的類導致了非常多的文字說明,把API簡化得越多,需要的文字說明就越多。經常性的,幾百個API函數被組織成一打或一百個類,每一個類具有幾十個或幾百個重疊的屬性、方法和事件,整理或者浏覽這個類將是工作量非常大的一件事。
設計和維護一個把各種API打包的類庫,是一件工作量非常大的事情,設想現在要寫一個類庫來簡化一個為Web服務的Html的網頁的生成過程,這就需要先考慮一下自己是不是需要知道"Html";是不是需要把這些諸如"tags"或"angle brackets"的概念隱含掉;是不是向用戶顯示一些諸如字體等這些屬性;是不是只要能處理Html就行了,還是需要處理XMl,能不能叫XHtml?等等。
假設一個基本類含有一個MustOverride方法叫做ParseHtml,然而當需要支持XMl時,就必須把Html支持替換掉,此時需要把方法ParseHtml清空,惟一的選擇是建立一個ParseXMl方法,但不能把ParseHtml方法移除掉,因為當把該方法移除以後,就會導致Html業務的喪失,因為整個類是分布的,用戶必須讓ParseHtml和ParseXml方法同時存在,但這讓內存和運行時間效率都減低了。
諸如這種問題在網絡開發環境還是存在的,用戶可能是過早設計了Html類庫,也許一個基於接口的系統可以更好的解決這個問題。它讓用戶建立一系列的Html接口,當在確認XHtml或者XML是更好的基底時,就可以放棄或改善Html接口。
2.從基本類中得到重用的代碼
使用繼承的一個重要的原因就是為了代碼的重用,然而這也是最危險的。正是因為代碼的重用,即使是設計的最好的系統,有的時候也會出現一些設計者難以預料的改變。
一個經典的表示代碼重用的高效率的例子就是一個數據管理庫,設想現在有一個大型的應用程序,用來管理幾個在內存中的清單,另一個是從顧客的數據中拷貝來的清單,該數據結構可以如下所示:
Class CustomerInfo Public PreviousCustomer As CustomerInfo Public NextCustomer As CustomerInfo Public ID As Integer Public FullName As String 'Adds a CustomerInfo to list Function Insertcustomer As CustomerInfo ... End Function 'Removes a CustomerInfo from list Function DeleteCustomer As CustomerInfo ... End Function 'Obtains next CustomerInfo in list Function GetNextCustmer As CustomerInfo ... End Function 'Obtains previous CustomerInfo Function GetPreCustomer As CustomerInfo ... End Function End Class 可能還有一張顧客的采購的東西的卡片清單,如下: Class ShoppingCartItem PreviousItem AS shoppingCartItem NextItem As ShoppingCartItem ProductCode As Integer Function GetNextItem As ShoppingCartItem ... End Function ... End Class
從這裡,就可以看見類的結構和模式,都有相同之處--insertions(插入)、deletion(刪除),以及traversal(浏覽),只不過是對不同數據類型進行操作而已,顯然,要維護這兩段幾乎具有相同函數的代碼很沒有必要,又一個顯然的解決方案就是把對清單的處理抽象出來,做成一個它自己的類,然後,就可用這個類派生出不同數據類型的子類,如:
Class ListItem PreviousItem AS ListItem NextItem As ListItem Function GetNextItem As ListItem ... End Function ... End Class
這樣的話,只需要對ListItem類調試一次,而且用戶只需要把它編譯一次以後,就不再需要管理關於清單管理的代碼,用戶只需要使用它就可以了:
Class CustomerInfo Inherits ListItem ID As Integer FullName As string End Class Class ShoppingCartItem Inherits ListItem ProductCode As Integer End Class
3.對不同的數據類型使用相同名稱的類和方法
一個決定是否要使用繼承辦法就是是否有以下問題:
(1)是否需要對不同的數據類型進行相似的操作;
(2)需不需(想不想)訪問程序源代碼;
舉一個非常具有代表性的例子,就是畫圖的軟件包,假想它可以畫圓、線和長方形。一個非常簡單的而且效率很高的方法就是不需要使用繼承,而用Select Case語句來實現,如下:
Sub Draw(Shape As DrawingShape,X As Integer,Y AS Integer,_ Size AS Integer) Select Case Shape.Type Case shpCircle 'Circle drawing code here End Case Case shpLine 'Line drawing code here End Case ... End Sub
但是這樣處理的話將會出一些問題,將來如果需要添加畫橢圓功能的話,那麼就必須重新改寫代碼,也許最終用戶不需要訪問程序的源代碼,但是可能畫一個橢圓還需要一些參數,因為橢圓需要的參數是一個長半徑和一個短半徑。但是這些參數又和其他圖形的參數無關,如果現在又要畫一個折線(有多條線組成),則又需要加入一些其他的參數,而且這些參數又各其他的圖形的參數無關。
繼承就可以非常好地解決這些問題,一個設計得很好的類可以給用戶留下一下很好的空間(MustInherit方法),那樣的話,每一種圖形都可以滿足,但是一些類的屬性(諸如XY坐標)可以作為基本類的屬性,因為這個屬性是每一種圖形都需要的,如:
Class Shape Sub MustInherit Draw() X As Integer Y As Integer End Class
其他的圖形都可以繼承這個類,如要畫一條線,代碼如下:
Class Line Extends Shape() Length As Integer Sub Overrides Draw 'Implement Draw here End Sub End Class 長方形的類,又應該如下表示: Class Rectangle Extends Line() Width AS Integer Sub Overrides Draw() 'Implement Draw here End Sub End Class
它的繼承子類(例如:長方形)能夠在圖象類的二進制的文件的基礎上繼承,而不是在基礎類的代碼的基礎上繼承。
4.基類的方法需要改變
Visual Basic(以及其他的提供繼承的編程語言)使得用戶有能力重載基本類的方法,只要在派生類中可以訪問的基類的方法都可以被加載(除了那些NotInheritable)。假如用戶感覺基本類的DisplayError方法需要給出一個錯誤號,而不只是錯誤的描述,則用戶可以在派生類中改變這種方法的行為,則派生類會調用它本身的方法,而不是基類的方法。
5.通過改變一個基本類,就改變了所有的派生類
繼承的一個強大的作用就是當改變一個基本類的屬性時,就改變了所有的派生類的相應的結構,但是,這也是一個危險的行為--有可能改變了自己設計的基類後,別人由該基類而派生出來的派生類就會產生錯誤。