物件導向的設計觀念從開始流行至今,在軟體開發的領域中已經有了舉足輕重的地位。在過去我們會將「物件導向」這個詞掛在嘴邊,但現在很少人這麼做了,因為它早就融入許多人的開發生活中,成了不可或缺的一環。

經歷了這麼多年的實際運用,究竟物件導向的設計方式,對於真實的開發帶來了多少好處?在這些日子以來,物件導向設計方法除了持續壯大之外,其實也不乏抱怨或是批評的聲浪。或許,我們也可以試著來回顧在我們的設計中,物件導向設計方法究竟存在那些問題。

實際使用時會出現的狀況
我最早聽到關於物件導向的批評,大致上是來自於物件導向程式語言的效能,或者是佔用的記憶體空間,更早以前,甚至也有人抱怨用物件導向程式語言所寫成的程式碼太大。關於這幾項問題,可能需要分開來解釋及探討。

首先是程式碼的問題。

物件導向程式語言不意謂著程式碼一定大,例如,Java 類別的bytecode其實大小都很小,當然,除了語言上Java本身就是朝向簡短程式碼在設計之外,平臺及內建程式庫的存在,也是Java程式碼佔用空間可以如此之小的原因。當然,採取另一個相反哲學的C++,以效率為最大考量、用空間換取時間,所以編譯出來的程式就會比較大。這一點從template(模版)的機制就能夠明白。此外,因為不倚靠像虛擬機器之類的運行環境,那麼就必須把為了支援物件導向的諸般機制,放到編譯出來的執行碼中,這樣也會增加程式碼的大小。

接著是運行效能較慢,以及記憶體佔用空間較多的問題。

C++當初的企圖,就是讓物件導向的精神,能夠在當時硬體效能尚不夠出色的時空背景下,被運用在實際的開發中,使得物件導向的想法,能夠開始提供助益。也因此,C++在純粹的物件導向,以及現實的考量中做了一些取捨,而這些取捨大致上都和效能以及記憶體佔用空間有關。事實證明了C++達成了它的設計目標。C++的問題,在物件導向及實務的開發中取得一個良好的平衡,也成功引領許多設計者踏入了物件導向的領域。

即使如此,效能及空間佔用的問題還是存在,只不過在C++上沒有那麼嚴重而已。到了Java、C#這種用虛擬機器來提供運行環境的語言,效能及空間的問題,當然理應更為嚴重。不過,Java的佔有率還是如日中天的快速成長,這背後的原因是什麼呢? 這有很大的比例,是因為硬體效能的快速成長,使得運行效能及空間佔用的問題在許多應用中,不致於構成瓶頸或是成為關鍵的影響因素。

帶來更強的生產力,但不熟的人容易誤用

為什麼開發者寧可選擇物件導向程式語言,因而犧牲一些運行效能及佔用空間?當然,不可諱言的,物件導向程式語言已成主流是一個重要的原因,不少開發者還是受到從眾效應的影響,才會使用物件導向程式語言來進行開發。不過,讓大眾寧可犧牲效能的因子,而選用物件導向的方式來設計,總的來說還是因為它的確對生產力能起作用。機器的效能在前二十年來,相較而言是容易提升的,軟體的開發生產力卻不見得。

生產力是物件導向程式語言預計帶來的好處之一,我認為實務上可以算是驗證了這一點。即使你不是使用真正的物件導向語言來做設計,物件導向觀念背後的精神,對生產力的提升還是有幫助,因為這些想法的確有捕捉到生產力的問題。 不過,許多人對物件導向設計有一個假設的成立,可能就不是那麼樂觀了。

物件導向利用物件的方式來建立解題空間中的模型,聽起來相當炫,也就是說,我只需要把我想要解決問題中的資料,都利用物件來表示,接著再利用物件的屬性以及函式來描述個別物件的行為,最後讓物件們之間彼此相互互動,就可以解決問題了。這種概念看似十分直覺,所以很多人都會認為利用物件導向的方式來設計,會因為符合人看待世界的方式,而更容易做設計。

只不過這一點到目前為止,看起來似乎不能得太多的認同。相反的,不少人認為物件導向的設計方式,學習曲線並不理想,並不適合初學者運用。甚至,對於不能體會物件導向精神、或是不熟悉設計方法的人來說,很容易產生誤用。

例如,滿多人都經歷過的——濫用繼承。

繼承是物件導向的主要支柱之一,而且這樣的名詞、這樣的概念,乍看之下也很迷人。在不少人的想法裡,「只需要繼承就可以直接使用已經寫好的程式碼,達到很好的程式碼重複運用(code reuse)」。所以,當我們剛開始試著利用物件導向來做設計時,我們常常會自己建立一個不小的繼承體系出來。但事後都會證明那多半是不必要的。

現在,通常會鼓勵人們使用「合成(composition)」來取代繼承在程式碼重複運用的作用。所謂的繼承是一種is-a(是一種)的關係,而合成則是has-a(有一個)的關係。當我們利用一個類別來合成其他類別時,也同樣可以使用所合成類別的程式碼。在我們許多需要重複運用程式碼的情境裡,我們需要的其實是合成而非繼承。

繼承的誤用似乎是不少初學者的共通症頭,這種問題就像是在設計一部車時,因為設計者想到車子要有引擎動力輸出的能力,於是就有可能讓車子去繼承引擎。但問題是,車子和引擎的關係是has-a而不是is-a。

繼承的例子只是眾多例子中的一個,在實務上,要能妥善的運用物件導向來做設計,需要花上不短的時間來學習,否則有時候往往是適得其反。就像是有些人也會把一些概念做得太過火,例如將資料抽象化是好事,但是他們引入了太多層次的抽象化,使得繼承的層次太多、太複雜,也有可能造成類別數的爆炸。因此,除了學習曲線通常比想像中的漫長許多之外,必須具備更好的知識才能辦法得到好的效果,也是物件導向設計的障礙。

此外,並不是所有的問題都適合描述成為物件。但許多物件導向語言(不包括C++)會要求必須這麼做,因為它們假設「萬物皆物件(Everything is an object)」,所以這些語言中並沒有純粹的函式,所有函式都是必須附屬在物件之下的。

在這種情況下,目前許多人的解決方式便是撰寫一個靜態的類別,裡頭就是一堆的靜態的函式。例如,有些人撰寫所謂Helper類型的類別,便是在提供此種作用。這個限制有好有壞,往好的方向想,你多了一個命名空間來將一些純函式聚集在一塊。然而,不好的影響就是得寫下額外的程式碼,像是類別定義之類的,而且這其實並不是做為抽象化的物件來使用。

總的來說,我個人認為物件導向對生產力應該還是有幫助的。即使你不用物件導向程式語言,但是它所帶來的設計觀念,都會很有幫助。只不過,憑藉著物件導向的方法所得到的生產力,並不是那麼容易取得。

首先,你得花上一段不短的時間來學習,並且練習將思維做轉換,此外,有一些容易發生誤用的地方,也必須小心。最後,每個人適合的解題思維模式都有所不同,例如:或許我對物件導向的結論如此樂觀,也是因為我已經十分習慣物件導向的設計方法了吧。

作者簡介


Advertisement

更多 iThome相關內容