很有意思的一篇文章



我節錄前面的一部分
完整版本(包含程式碼)
請參考原始文章

什麼是物件導向(Object Oriented)?一個好基本的問題,卻困擾了我10年之久...

或許是我的駑鈍,物件導向困擾了我10年之久,1995年使用Visual FoxPro 3,VFP3已經是物件導向語言,繼承,封裝沒問題,至於多型,我不太確定是否支援,當時也搞不懂多型,所以也沒用VFP3寫過多型,從VFP3,VFP5,一直用到VFP6,接下來用VB6,VB6沒有繼承,但有interface,若要說也算是有interface繼承,也算有多型,不過當時還是沒搞懂,所以VB6不算是完整的物件導向語言,但卻是物件基礎的語言(Object Based),到了C#時代,C#無疑是完整的物件導向語言,繼承、封裝、多型通通有,不過由於C#過度的語言簡化(Syntax Sugar),多型用起來沒有什麼感覺,一直要到我這學期學了C++後,我才完全體會了什麼是多型。

首先一個基本的問題,為什麼稱為物件導向?明明C++強調的是class,稱為『類別導向』不是更適當嗎?

其實OOP的精隨是在多型(Polymorphism),侯捷這樣說過,C++ Primer也這樣說過,所有OOP所規定的一堆機制,目的也只是為了實現多型,多型利用繼承(Inheritance)和動態繫結(Dynamic Binding)實現,其目的是讓程式能在run-time才決定物件型別,所以同一個程式,只要其所能處理的物件屬於同一個繼承體系(Inheritance hierarchy),則不須修改程式就能正常執行。

為什麼要提供這樣的機制呢?開發軟體最常遇到的問題就是『需求改變』,假如是完全新的需求還沒話說,就重新寫新的程式即可,最怕的就是只是小改變,如有90%的功能和原本一樣,只有10%不一樣,所以之前90%的程式必須留下來,剩下的10%就改寫,若你覺得這樣很簡單,那你肯定是外行人了,沒真正寫過大程式,另外90%的程式可能是別人寫的,要想改原來的程式,可能必須看懂之後,才能下手該怎麼改,難怪很多人說,要看懂別人的程式,還不如重寫來的快,但實務上,Boss不可能讓你重寫,一來等於完全放棄之前的投資,二來原來的程式已經穩定,重寫等於得重新測試。物件導向就是希望當你有新的需求時,可以不必去更改原來的程式,只要繼續新增你的需求即可,既然不用更改原來的程式,就不會有維護的問題,卻又可達成新的需求。物件導向怎麼做到的?就是靠『多型』,所以物件的『多型』才是物件導向的精隨,類別(class)只是達成多型中的手段而已,所以該稱『物件導向』而非『類別導向』。

物件導向在內地稱為『面向對象』,其實翻的很傳神,傳統如C語言的寫法屬於Functional Decomposition,也就是依功能來分析而如C++、C#、Java屬於Object Decomposition,是依『對象』,依『角色』來分析,這兩種寫法有什麼差別?若依功能來分析,將來有新的需求,就是再多寫一個function,然後在main()或其他function中用if做判斷什麼樣的對象,角色該執行什麼function,這種寫法勢必要更改原來的程式。

物件導向是依對象、角色來分析,若有新的對象要加入,只要加上描述該對象的程式即可,原來的程式不需再更動。

這樣講或許很抽象,我在這透過一個簡單的範例分別用Functional Decomposition和Object Decomposition的寫法來寫,各位就可明顯的看出物件導向中多型的威力,我先用C++來寫這兩個程式,最後我會用C#改寫。

程式架構很簡單,想列出一個研究室中每個成員作了哪些事情,原來研究室中只有大學生和碩士生,但後來教授也收了博士生,所以得修改程式,所以看看用這兩種寫法的差異。

首先看Functional Decomposition的寫法,當要加入博士生時,除了在41行和44行要加上博士生要做的事情外,最大的問題是在47行和59行之間必須加上對博士生的判斷,如此必須更改到原來的程式碼。

再來看Object Decomposition的寫法,52行和58行加入博士生的描述後,這樣就好了,79行和88行完全不需修改程式碼,而且程式中完全不需要用if做判斷。

為什麼這麼神奇呢?這就是物件導向的『多型』,透過多型,程式會在run-time自動判別是大學生、碩士生、還是博士生,然後自動執行大學生、碩士生或博士生相對應的程式碼,所以我們不用另外去維護既有的程式碼。

C++是怎麼達到多型的?用的就是『繼承』(Inheritance)和『動態繫結』(Dynamic Binding)。

注意15行到30行特別設計了一個student的abstract base class,你可視為C#或Java的interface,然後33行到40行的大學生class,42行到48行的碩士生class都繼承了student,這樣子的繼承有什麼用呢?繼承是一種is a的關係,大學生是一種學生,碩士生也是一種學生,所以大學生和碩士生都是學生,既然都是學生我在65行中的add(Student&)就可以這樣寫,這表示我必須輸入Student型別物件的Reference,但既然大學生也是學生,碩士生也是學生,所以理應也可將大學生和碩士生傳給add(Student&),開了這個後門後,等於開了『多型』的第一道巧門,我們可以將同一個繼承體系(Inheritance hierarchy)的物件傳進function了,而不再限制只能傳一種物件。

但這樣還不夠,要怎麼讓程式在run-time自動判斷該跑大學生的job()還是碩士生的job(),若還是得用if()判斷,那將來還是得修改程式,多型的意義就不大了,別忘了剛剛我們是傳reference進來,reference其實就是pointer,只是他是一個不用dereference的smart pointer,所以我們傳進了大學生物件和碩士生物件的記憶體位址,有了記憶體位址,程式就自然可以找到相對應的function了,這就是『動態繫結』(Dynamic Binding),說穿了其實也沒那麼神奇了,:~D,真的如一句閩南語的俗話所言,『江湖一點訣,打破無價值』。

所以才說『多型』(Polymorphism)是靠『繼承』(Inheritance)和『動態繫結』(Dynamic Binding),而動態繫結又是靠reference和pointer完成的。

除此之外,我們還看到了Abstract Base Class / Interface,看到了virtual/override,所以物件導向中種種的機制,其實只是為了實踐多型而已。

首先感謝網友Allen Kuo 對以上C#程式碼的建議,改用abstract class而不是interface,這樣C#的程式碼將完全和C++程式碼意義一樣。

13行用了abstract class寫法,30行由derived class呼叫base class的constructor,C#使用了base這個keyword。57行的List為C# 2.0新的寫法,很類似template寫法,提供了類似C++ STL vector的功能。59行的add(Student student)是C#提供多型的地方,很明顯的不須reference,只要傳進物件即可,剩下的compiler幫你處理了,程式是乾淨很多,但卻無法如C++那樣感受用reference/pointer達成dynamic binding,所以我覺得,對初學者來說,還是得學C和C++,pointer雖然難用,但卻是很多機制的原理,直接學C#或Java這類語言,雖然語法乾淨,卻無法體會出背後的原理,這也是我10年來一直無法體會多型,一直要看到C++的Dynamic Binding之後,我才很放心的完全了解多型是怎麼做出來的。

65行和66行也明顯比C++乾淨很多。

另外一個問題,物件導向和資料結構/演算法相衝突嗎?

一點也不衝突,資料結構/演算法類似學習武功,而物件導向類似學兵法,是學萬人敵的工夫。一個資料結構/演算法高強的,是獨來獨往的大俠,而物件導向高強的,是擅長行軍佈陣的大帥,只有在大程式、大專案中,才可以顯現物件導向的威力,正如『韓信點兵,多多益善』一樣,所以資料結構/演算法和物件導向都很重要。

最後一個問題,那物件導向該怎麼學好?

簡單的說,就是去學Design Pattern。學好C++,只學到了物件導向的語法而已,至於該怎麼用,就是去學Design Pattern,裡面有一條條的兵法讓你去用。下學期陳俊杉教授要開的就是Design Pattern,很期待下學期趕快開學,讓我能一解10年來物件導向的渴望。


smartPG 發表在 痞客邦 PIXNET 留言(0) 人氣()