當前位置:編程學習大全網 - 編程語言 - 簡述對象設計是如何有助於代碼重用的

簡述對象設計是如何有助於代碼重用的

在“代碼的壞味道”中,代碼冗余是其中的壹條。而應對代碼冗余最基本的方法就是代碼重用,並且在面向對象中,重用也是壹種基本的思路。

當面向對象設計成為主流時,“重用”曾經被吹捧為面向對象的主要優點之壹。為了實現“重用”,教科書總是告訴我們,應該找到已有的東西,用派生類的形式對其進行小幅修改,或加入新的特性,實現代碼的“重用”。所以現在壹提到面向對象的代碼重用,大多數程序員想到的是面向對象的“繼承”。真的是這樣的嗎?我們可以看看下面的壹個例子:

現在有壹個項目:對動物的不同特征建模。項目的需求如下:

1、 每種動物都有數量不同的腿;

2、 動物對象必須能夠記住並獲取這壹信息;

3、 每種動物的移動方式不同(考慮行走和飛行兩種情況);

4、 對於給定的地形類型,動物對象必須能夠返回從壹個地方移動到另外壹個地方所花費的時間。

處理“腿數”這個變化,很簡單,也很典型,就是用壹個數據成員存放這個值,並且設置該值的讀取和賦值方法(在Java中也稱為getter/setter方法)。但在處理動物移動方式上,通常有兩種方法:第壹種是用壹個數據成員說明動物對象擁有哪種移動方式(即開關變量,在本文中暫不討論開關變量的問題);第二種是用兩種不同類型的Animal類(都派生自Animal基類),因為Animal的大部分特性是相同的,僅移動方式不同而已,為了代碼的重用,用繼承來處理不同的部分,如下UML圖:

到目前為止,該設計沒有暴露出任何問題。但現在,需求發生改變了(這在軟件開發是無時無刻不在發生的事情),不僅要考慮動物的移動方式,還有考慮動物的食物屬性(肉食動物、草食動物、雜食動物)時,問題就會變得非常的復雜,因為有會飛的肉食動物(老鷹)、有會行走的肉食動物(獅子、老虎)、也有會飛的草食動物(麻雀)、會行走的草食動物(牛),甚至還有行走的雜食動物(烏龜),那麽在之前的設計上繼續使用繼承來實現代碼的重用,那麽會得到如下的設計UML圖:

現在我看發現,子類的數量爆炸性的出現(這裏還沒有考慮雜食動物的情況),如果繼續有動物的特性需要被考慮進來,那麽,這個繼承關系將會更加復雜,子類數量將巨大,給後期維護帶來巨大的麻煩。

采用繼承來實現代碼重用,它能夠這次湊效,但是無法次次湊效。諸如此類的反復特化,要麽會使代碼變得無法理解,要麽產生冗余。特化技術最終會產生太深的繼承層次。糟糕的是繼承層次太深導致程序難以理解(弱內聚)、存在冗余、難以測試而且多個概念耦合在壹起。無怪乎許多人認為面向對象有些言過其實——尤其是這壹切都是因為遵循了面向對象“重用”要求。

那麽我們真的束手無策嗎?《設計模式》的大師們的話仿佛又在耳邊回蕩:“考慮設計中什麽應該是可變的”、“對變化的概念進行封裝”,並且最重要的是“優先使用對象聚集(組合),而不是類繼承”。大師的話仿佛給了我們很好的提示:尋找設計中的變化,並封裝在壹個類中;將這個類包含在另外壹個類中。

我們還是考慮上面的那個例子。什麽是變化的:動物的腿數、動物的行動方式、動物的食物屬性,這些是變化的。找到了變化,下壹步就是進行封裝了,動物的腿數我們已經得到了很好的處理。那麽來考慮動物的行動方式和動物的食物屬性。可以考驗下面的設計:

將動物的行動方式和動物的食物屬性分別封裝在AnimalMovement和AnimalFood這個兩個類中,它們的變化體現在各種的子類中,這樣,動物的行動方式和動物的食物屬性就不會方法任何的關聯(其實它們之間本來就沒有關聯關系),在Aimal類中,持有AnimalMovement和AimalFood的引用,當AnimalMovement和AimalFood發生變化時,也不會影響到Aimal類。

我們現在再反過頭來看看動物的腿數的這個屬性,其實這個也是壹個變化,好像我們對它進行特殊的處理,但是仔細想想,我們也是將其封裝在了int中,只是這是壹個數據變量。但是真正的面向對象語言中,萬事萬物皆對象,甚至內置的數據成員也是對象(如果我們將int換成Integer,或許更容易理解壹點)。

其實重用並不是使用面向對象方法的原因。降低維護成本和使代碼更加靈活,更容易擴展,才是更重要的考慮因素。使用正確的面向對象技術當然可以實現重用,但並不是通過直接使用該對象,然後由它派生新的變體對其重用即可達到的。這樣做的結果是產生難以維護的代碼。所以:對象的真正威力並不在於繼承,而是來自於封裝行為。但我們往往錯誤的使用前者而忽略了後者。

那麽被面向對象“鼓吹”的繼承就沒有作用嗎?其實不是,反而相反,繼承擁有很大的作用,沒有繼承就沒有多態,沒有多態,那麽上面的第二個設計方案也無法實現。繼承本身並不是什麽特別難的技巧,但是要能夠把繼承運用的好卻是很難的。那麽繼承該用在什麽地方呢?讓我們在來聆聽壹下大師的教誨:在OO的學習中,繼承占有很重要的地位,但這並不是說可以到處濫用。相反,運用繼承的時候,應該盡可能的保守,只有在它能帶來很明顯好處的時候,才能使用。在判斷該使用合成還是繼承的時候,有壹個最簡單的辦法,就是問壹下自己,是不是會把子類向上轉型為基類。如果必須轉型,那麽繼承就是必須的,如果不是,那麽就該看看是不是不該使用繼承。

“優先使用對象聚集(組合),而不是類繼承”,請永遠記住這句話,並將其運用到實際項目中。繼承和合成都能讓妳在已有類的基礎上創建新類。但通常情況下,合成是把已有的類當作新類底層實現的壹部分來復用,而繼承則是復用其接口。盡管面向對象的編程會反復強調繼承,但是當妳著手設計的時候,通常情況下還是應該先考慮合成,只有在必要的時候才使用繼承。合成會更靈活。

參考資料:

《Java編程思想》

《設計模式解析》

《重構——改善現有代碼的設計》

《設計模式:可復用面向對象軟件的基礎》

  • 上一篇:開源編程競賽平臺
  • 下一篇:匯編語言是壹種
  • copyright 2024編程學習大全網