2009年4月21日 星期二

關於OOP 物件導向 (About OOP)

這篇文章的目的是粗略的介紹物件導向程式設計(object-oriented programming) - OOP。

關於這個主題你必要知道的一件事就是「九層之台,起於累土。千里之行,始於足下(Best way to begin a long journey is to make a first step)」-中國諺語。 把它想作我們要無痛的跨出第一步。

我假設你已在 Pascal, C 或某些『通常』程式語言寫過程式同時你覺得有某些事情遺漏。這個『某些事情』就是高層次的抽象。OOP不只是把它想成程式設計的技術。當然,如果程式語言直接支援OOP是最好的--這樣可以讓你能夠以語言本身的語法思考。但既使是非OO的語言,以物件的方式思考也是可以的。

我要引用C++之父Bjarne Stroustrup的話: 小的程式(1000行)可以使用任何東西或任何方式來撰寫。如果你不是那麼簡單,最終你可以讓程式跑。但是,大型程式就不一樣了。如果你不是使用好的『程式設計技術』(尤其是OOP),在你修改舊有的錯誤時新的錯誤隨之產生。

源由是:程式的每一部份相依於許多其他的部分,而這些相依關係是如此的複雜及非直覺性的,既使一個程式設計師撰寫一支程式也沒辦法掌握追尋這些相依關係。當你改變一部份,你沒辦法很容易的看到對於程式其他部分的影響。當然;使用良好的程序性語言(procedural programming),能夠模組化程式及擴充可重複使用程式碼,但如果你是這樣做的話,你已是開始以物件導向思考了。此時你唯一忽略的是以較好的方式組織這些你所寫的程序及方法,並且把這些程序及方法與其操作的資料結構連結在一起。

讓我們回顧在學校所學的,並且溫習資料型別(data type)的定義:「資料型別是成對的(Pair),第一個元素是這個型別所容許的所有值的集合,第二個元素是我們可以執行於這些值的所有操作。」資料型別是具有值及操作的二元性(duality)。

如果你知道甚麼是資料型別,你便知道甚麼是OOP的基礎概念:類別(class)。

事實上,類別不只是以程式語言的方式定義一個資料型別。

把下面的概念在你心中組合起來:一個類別是程式是語言所支援介於函數/程序與資料結構詳盡的(explicit)練結。現在有一個邏輯問題產生了:「但是,我可以在程序的參數放入任何東西--包括我想要的資料結構--而且這是我把資料結構連結到操作的方式。這有甚麼不同?」差異就是上面所說的『詳盡的(explicit)』。在OOP;屬於類別的程序有許多不同特定(special)方式,同時經由這些特定的方式你可以在『單獨(stand-alone)』的程序做一些不可思議的事情。這些『成員(member)』的程序就叫做方法(method)。

所以,為什麼我需要把資料操作連結進來?主要有下列4個優點:

第一個優點:乾淨的名稱(clear names)
如果你有一個類別Triangle(三角形)及方法Paint(這個方法可以呼叫用以繪至三角形),你也可以有一個類別Square(正方形)也有一個方法Paint(這個方法繪製正方形)。既使有相同的名稱,這些方法還是不同的。所以,節省名稱並且避免衝突。模組(modules)(在Pasical中的單元(units))可以做為這個用途的使用,同時有些語言提供區隔的機制以克服邏輯名稱(例如C++命名空間Vnamespaces)或Modular-2廣泛的實作模組)。但是模組在任務中往往太『沈重(heavy)』,尤其是如果(在Pascal中)受限於原始碼的實際儲存體--檔案。舉例來說在Modula-2的模組可以包含次模組。這些次模組明確地定義哪些他們可以看以及從外界可以被看到,但這些看起來像是嘗試以鐵鎚打一隻蒼蠅。此外如果你有一個變數t是屬於Triangle類別以及一個變數s是屬於Square類別,你以t.paint繪製三角形而以s.paint繪製正方形--你想還有比這更簡單的嗎?

因此我來到物件(object)這個名詞。簡單易懂,物件是某個類別的變數。另一個物件的名稱是實例(instance)。類別是型別(type)的定義,物件是類別的一個特定實例(specimen)。例如,當我們說t有型別(類別)Triangle,另一種說法是:t是Triangle的物件(實例)。所以t與s都是物件。

第二個優點:封裝(encapsulation)
每一個類別可以隱藏他的某一部份。如果你有一些輔助的部分程式碼;這些程式碼只是供類別本身的方法呼叫,你可以讓他成為私有方法(private method)--在類別外部無法看到。

如果你是撰寫大型程式,你使用模組並且隱藏這類程式碼在實作段(implementation section)(在C++中是cpp檔)。無論如何;你是在大量的模組下及每一個模組使用其他每一個而使自己冒相當的風險,而且這些模組相互關連(interconnected)不會是你所喜歡的......

隱藏程式碼稱為封裝。有些語言例如C++及新版的Java甚至可以讓類別中還有類別,而這些『被包含』的類別是其包含類別私有類別;這些是沒有OOP則沒辦法辦到的。

直到現在;應用許多自我訓練(self-discipline);你只可以經由使用傳統的『好的程式設計』技術自保。現在有一些東西沒有OOP根本是不可能達成的。

第三個優點:繼承(inheritance)
最重要的優點就是:繼承是擴充或改變既有『父』類別的方式,並且讓『子類別』仍然連結其父類別。這是程式碼再使用的關鍵。

如果你已有一個類別只是與你所需的有些微的差異,為什麼你不使用這個既有的類別,而只是增加差異的部分?當然,使用複製貼上程式碼,你也可以快速的『再使用』程式碼。但是這種複製貼上的方式你產生了兩個程式碼複本(copies)其中你只知道他們應該做相同的事情--亦即你必須同時維護這兩個複本。想像一個特殊情況就是你有10個複本!我必須提醒你:那並非不尋常;許多類別程式庫(像Delphi的VCL)有好幾打的繼承階層包含幾百個類別。如果那是以複製貼上程式碼的方式那是多麼複雜,而我們還尚未說到效率的問題呢。

無論如何,當你使用繼承,如果你改變父類別的任何東西,你可以自動的改變到子類別(及子類別的子類別等等)。從另一方面而言,在多數的語言,你可以產生一個新的子類別而無須重新編譯父類別,甚至無須父類別的原始碼--只需要介面就可以(類別及方法的宣告而沒有定義)。因此,沒有重複編譯相同的程式碼,同時所有的事情都是比較快速且簡單的。

但是使用繼承,你可以增加新的方法及欄位;或者修改現有的方法。我們將在後面更詳細的討論繼承。

第四個優點:多型(polymorphism)
與繼承緊密相關的概念就是多型。多型的觀念對於OOP的新手可能比較難領會,所以如果你一時無法全然瞭解也不要太緊張。當你從一個類別繼承,你(在實體層(physical level))增加/修改方法或者增加欄位,這就是多型。

但是在邏輯層(logical level)你是產生了類別的一個次種類(sub-kind)。

現在,小姐先生,一個多型的更進一步的想法(meta-though):子類別就是父類別(child is parent)。這表示:你可以在父類別可以使用的地方使用子類別(既使你或許也可以在其他的地方使用子類別),這很簡單因為子類別包含所有父類別所包含的(既使子類別或許包含多一些)。請記得反向則否。父類別不是子類別(parent is not child)--子類別總是包含一些東西是父類別所沒有的,因此我們不能以父類別替代子類別。

現在,多型的本質:既使我們以子類別替代父類別,他仍然是子類別。如果其中有一個類別是屬於子類別及父類別,哪一個版本將會被建立是依據你在執行期所使用的類別。如果我們使用子類別,那麼子類別的方法將被呼叫。例如;不管我們宣告的是父類別的變數,在執行期我們可以在此以子類別取代,而子類別將仍然是以子類別的行為工作。

此外,可能的子類別/父類別替代是在OOP中使用指標的原因。

因此,不同類別的物件可以以相同的手法使用,但依據他們實際的類別而有不同的行為。相對的,我們可能沒有決定特定物件的實際類別,但在執行期,正確類別的方法將被呼叫--而不是編譯期所宣告的類別。

結論
讓Bjarne Stroustrup再次幫助我們:程式語言有兩個觀點:

首先是告訴機器要做甚麼的技術部分。
其次概念工具;你可以用於思考如何解決問題。

我所寫的理論,不只是包含於每一個主要的物件導向語言,也是代表抽象思考傑出的工具。你不想壓抑你在流程圖的創造性,你將進一步偏離及以物件切割問題;而這個物件是由問題所行成。你將物件分門別類成為類別,並將類別組織成邏輯的樹狀(tree-like)的結構--類別層級架構(hierarchies)。

沒有留言:

張貼留言