注:前段時間寫給公司同事的一篇面向對象的入門文章(香港公司,同事們一直在用VB6,聽起來是不是感覺有點不可思議)

    來公司之前,沒接觸過VB,只知道和Delphi一樣,是一個優秀的RAD開發工具,但和Delphi強大的面向對象能力不同的是,VB不支持面向對象的功能,屬於傳統的RAD開發工具。最初對VB的感覺就是這樣的,膚淺而又片面,在公司使用VB的這段時間內,越來越發現當初對VB的認識有失偏頗,面向對象的幾個重要特點,基本上都可以在VB6實現。這篇文章,一方面是對我自己當初對VB錯誤認識的一個糾正,加深自己對VB中面向對象功能的認識。更重要的一個原因是:希望這篇文章能起拋磚引玉的作用,使得部門同事之間在信息交流,技術共享方面做得更好。畢竟思想的分享具有傳遞和累加性,交流和探討的過程,也是加深理解,糾正錯誤觀點,清晰思路,提高自己的過程。好了,閒話少說,讓我們進入正題!

一:面向對象(Object-Oriente)編程的基本概念

什麼是面向對象編程呢?首先,面向對象編程與結構化編程相對而言,是結構化編程語言發展到一定階段,無法解決軟件規模越來越大而出現的軟件危機,在其基礎上形成的。結構化編程是一種編程方法,是用計算機的視角來抽象問題的方法,關注的是特定問題的具體業務流程,面向對象編程也是一種編程方法,它采用從更接近真實世界的視角來分析問題,使用更接近人們理解真實世界的方法來抽象問題,模擬真實世界的方法,這種方法稱為“面向對象”(OO)

面向對象的本質是對真實世界的抽象與模擬,把要解決的問題如業務邏輯抽象成一個個的業務模型,業務的流向和邏輯通過業務模型的交互來進行處理和分析。類與對象是面向對象編程中最基本的兩個概念。類是對真實世界事物的模擬和抽象,在編程中可以認為是自己定義的數據結構,擁有自己的方法和數據成員,其中數據成員表示類對象的狀態,而方法是改變類狀態的操作。對象是類的具體實現。類是個概念,而對象是這個概念的實體。一個完全的面向對象的語言,至少要具備以下四個最基本的特征。

1:抽象 Abstract

2:封裝 Encapsulation

3:繼承 Inherited

4:多態                Polymorphism

1:抽象:抽象是對現實生活中相同類別事物的共有特征的高度概括。或許這樣的解釋比較枯燥,,也不容易理解。前面提到,面向對象是對現實生活事物的模擬和真實反映,那就以現實中的例子加以說明。想象一下鳥兒的形象,不用特定指哪一種鳥,只要是說到鳥這樣概念,在腦海中基本上就會出現如下的一個形象:有翅膀,有羽毛,有兩只腳,會飛,會唱歌的精靈。為什麼說到鳥我們就自然想到是這樣的,而不是一個頭上有角,身軀龐大,有四只腳的牛的模樣呢?這是因為有翅膀,有羽毛,有兩只腳,會飛,會唱歌是現實世界中各種各樣鳥的共同的特征,這樣的共同特征就是對鳥最生動的抽象。

由此可見,抽象的目的是為了提取共性,明確數據成員,清晰功能和方法。

抽象是面向對象建模中最基本的要求,在實際的程序設計對特定領域的業務建模時,一個適合的抽象能對建模的成功起到事半功倍的效果。

 

2:封裝。封裝是隱藏內部實現細節,只向外公布特定的訪問接口,減少操作的複雜性。封裝的目的是為了信息隱藏。比如天天使用的顯視器,有打開/關閉按鈕,通過這個按鈕,就可以實現打開和關閉顯示屏的目的,對於使用者來說,需要的是通過這樣的簡單按鈕就可以操作顯示器,根本不需要關心顯示屏的內部是如何通過這個按鈕真正實現對顯示屏的打開和關閉的工作。這是現實中的封裝。面向對象中的封裝主要的作用也在於些:隱藏實現細節,實現代碼的安全性。

 

3:繼承。繼承的含義是父類擁有的特征和功能,子類可以自動擁有。繼承的目的是為用代碼重用,縮短編碼時間,減少開發周期。世界萬物中,存在著許多生動的現象來告訴我們什麼是繼承,比如說鳥的一種:百靈鳥,因為屬於鳥這個類別,就自然擁有鳥兒有翅膀,有羽毛,可以飛翔,可以歌唱的本領。俗話中的“虎父無犬子”,“老鼠的兒子會打洞”等等,都是在用通俗但很精辟的語言闡述著什麼是繼承。

 

    4:多態。多態是面向對象語言中最難理解的概念,事實上也是使用最廣泛的一個特性。從字面意義來理解,多態就是一個對象具有多種形態。從程序實現上來看,多態是父類通過調用不同子類的相同方法,實現不同子類相同的操作,達到不同的效果,產生出不同的形態。還是以剛才的例子來說明吧,百靈是鳥的一種,黃鸝也是鳥的一種。它們都有由父親繼承下來的動作:歌唱。但百靈歌聲清脆悅耳,黃鸝歌聲婉轉動人。同樣的一個動作,不同的鳥兒會唱出不同的旋律,但我們都可以說是鳥兒在歌唱。這就是生活中的多態。正是存在著各種各樣的多態,窗外的世界才會如此多姿多彩,耳邊才時時響起醉人的燕語鶯聲。

 

二:VB6中的面向對象應用

初步了解了面向對象的基本概念後,現在我們來看看,VB6中面向對象三大特征的實現和應用。

1:封裝

   封裝應該是VB6中用得最多的面向對象的概念。封裝在面向對象語言中常表現為:屬性,方法和事件。屬性應該是VB中實現封裝最多的一種,具體實現很簡單,定義好一個私有變量,再定義好相應的Let 和 Get 屬性 就可以實現榜上相應的屬性。Get和Let分別對應著對這個屬性的Read(讀) 和 Write (寫)

   相關方法如下:

   Private sSomeValue As String

   Public Property Let SomeValue( Byval vData as string)

       sSomeValue = vData

   End Property

 

   Public Property Get SomeValue() As String

       SomeValue = sSomeValue

   End Property

  

2: 繼承

   VB6中通過implentments關鍵字來實現繼承。要注意的是,在VB中指定繼承後,對於父類所公開的的屬性和方法,必須要手動在子類中一個一個的實現。當然如果覺得手動編碼不方便的話,也可以通過IDE中的ClassBuilder類生成器幫助也可以。

   但有點奇怪的是,在VB6中,子類從父類那繼承的,只能是接口,而不能繼承父類中的實現。就算是接口,也要在前面加上父類的名稱才可以正確編譯。這樣看來,VB6中的繼承,實際上只能算得上的面向接口繼承,而不能稱為面向實現繼承了。面向實現繼承是可復用的基礎,VB提供的只是接口繼承,無法提供實現繼承,再加上也不能實現override和overload的功能,這些可能是VB中面向對象無法得到廣泛使用的原因之一吧

 

3:多態

   相對於繼承支持的不足, VB6對多態的實現是很成功。仔細思考後也就清楚了其中的原因,多態的實質是接口與實現分離,父類定義接口,子類實現接口。而VB中的繼承恰恰實現的就是面向接口繼承,所有的實現都必須在子類中完成。表現出的正是多態的特征。

 由於VB6中無法實現面向實現繼承,再加上訪問修飾符只有Private和Public兩種,沒有針對繼承的Protected。所以VB6應該不能稱上是真正的面向對象語言。幸運的是,在DotNet環境下,VB.Net實現了以上的所有不足,成為一個真正的面向對象語言。

 

三:讓我們看得更得更深入點

 1:封裝變化

 封裝的目的是實現信息隱藏,簡化訪問操作。但要明確的是,封裝的對象是什麼?或許大家都有過這樣的開發體會,在開發過程中,有時需求的一點變更會引起多處程序的修改,嚴重時會因為一個小小的修改而導致好多意想不到的Bug。用戶的需求可能千變萬化,理想的情況當然是在系統分析中把所有的業務邏輯和情況都考慮到,但業務發展無法預見,再完美的系統設計也無法解決未知的業務發展變化。而程序開發參考的是現有的項目設計文檔和開發文檔,所以業務的變更結果是程序修改無法避免。那麼如何做到程序設計中在滿足新的需求變化的同時,盡可能減少修改呢?現實中的封裝或許可以幫助我們找到部分答案:顯示器通過連接線與PC主機通訊,實現正常工作。以前使用的是15’顯示器,然後發展成17’純平,現在流行液晶了。但對於主機來說,不論顯示器的類型如何變換,大小如何改變,形狀如何變幻,但只要顯示器提供的連接線結構不變,主機就不用做任何的改動,繼續通過顯示接口與顯示器正常通訊。看,多麼簡單但又高效的設計,解決了人們對顯示器要求的不斷更新。在程序設計中,我們也可以學習這樣的思想來解決不斷出現的需求變化:封裝變化,將存在變化的業務邏輯封裝成一個或多個獨立的類,提供相應的功能接口供外部程序使用。但業務規則發生改變時,只須修改封裝這一業務邏輯的類,將修改所引起變更的范圍壓縮到最小。只要對外的接口函數沒有改變,外部與這個類交互的代碼就不用做任何修改,自然就不用擔心程序中其它代碼會因為這次的修改而產生更多的Bug。

 

2:狀態維護

    面向對象的封裝的目的是為了信息隱藏,提供代碼訪問的安全性。

對於一個類來說,封裝的結果對外表現為屬性和方法。其中屬性是類狀態表現,而方法表現出來是類能實現的功能和操作。

    類屬性與全局變量是有區別的,在程序運行時,同一時間內,全局變量只能有一個值,程序中所有訪問該全局變量的代碼,得到的是相同一個值。屬性不同,類在程序運行時必須要實例化成具體的對象才能工作,所有實例化的對象都有相同的屬性,但每一個對象的屬性值都可能不同。每一個不同的屬性值都可以看成是同一個類同一屬性不同狀態的表現。

 

 3:面向接口繼承和面向實現繼承

    面向基類繼承也就是面向實現繼承,表現出的是子類自動擁有父類的特征,自然界的遺傳特性基本上都可以看成是典型的面向基類繼承。那麼面向接口繼承該怎麼理解呢?我們可以把接口理解成是一系列概括性規范和標准,還沒有做定具體的規則。不同的實現者可以按照自己的實際情況去填充和實現。

举个例子,一老汉,做了一个面饼,给了他的几个儿子,老大用这个饼,加上点馅,包一下,做成了包子,老二用这个面饼,在外面刷上油,撒上料,烤一烤,做成了烧饼,老三呢,胡乱做,在饼上抹上乱七八糟的东西,烤一烤,做成了披萨饼。面向基类繼承就是这样,基类提供了基本的实现,子類就在其基礎上面添料,最终就成了每个人心中想要的饼。同時,因為老爸最初給的是面餅,所以做出來的東西都是由面構成的。
    面向接口则不一样,老汉没有给兒子們實際的面餅,而是寫了一個制饼的指南,然后把这个指南发给儿子们,儿子们按著制餅指南上的規則,各自按自己的想法去做實際的餅了。至於原料,兒子們可以用面,也可以用其它的東西。

 

 4: 多態的使用

   多態是面向對象設計中應用最廣泛的特征,多態的目的是在在繼承的基礎上又提供子類實現變異的可能性,可以這麼說,沒有多態,面向對象編程就失去最吸引人的部分。那麼,多態在實際的程序設計中如何使用呢? 

 還是以博客園中的一個比喻來加以闡述吧:

一日300多人一起打掃卫生,不懂多态的领導A发号施令道:張三去扫地、李四去擦窗户、王五去打水......话未毕,累倒当场,立仆。
   上级随即派来一懂得多态的领導B,不愧青年才俊,他只发布了一道命令:大家都去干活。于是各司其职,不一会事毕.....

 

   領導A之所以不幸,就是簡單的一個打掃衛生的任務,分派時複雜到每個員工,累死了。領導B吸取了A的教訓,用了一個統一的命令分派所有員工的任務,具體到每一個員工如何工作,則是由每一個員工自己根據實際情況決定是去掃地,還是擦窗戶,還是去打水。

    想想看,領導A發號施令的場景,多麼象我們在程序中用到的Case/If語句?分類情況很少時,用Case/If語句當然不會覺得有什麼不妥,但如果分類的情況有成千上萬種,請想象一下,你的程序到時會有多長,時間一長,又如何來維護?

 

5:抽象類與接口

    類的繼承表現出來的是“Is A “ 概念,表明的是“是一種….”,接口繼承表現出來的是”Can Do”概念,表明的是“能做….”,比如任何從鳥這個抽象類繼承出的類,自然是鳥的一種。接口定義的是一組規范,一組能力,繼承自接口,實現這組接口,就擁有處理這些接口的能力。

    一個類可以同時擁有多種不同性質的能力,當然可以從多個接口直接繼承。但類繼承不同,類無法從多個類直接繼承。百靈不可能是鳥,同時又是牛吧?

    “Is A””Can Do”是抽象類與接口最本質的區別,從這個區別直接導致抽象類與接口表現出的某些區別:

1:一個類只能從單個抽象類繼承,但可以從多個接口中繼承

    2:在接口中只能定義方法,但抽象類中除了方法外,可以有屬性

    3:接口的方法默認都是Public的,抽象類的方法不必全都是Public

 

6:虛方法與抽象方法

    OOP中,以abstract關鍵字標識的方法叫抽象方法,抽象方法只是定義一個方法聲明,沒有提供方法實現,表示抽象方法所屬的類擁有這樣的一種能力。

所有從該抽象類繼承的具體類,都必須實現這個抽象類的所有抽象方法。這樣的一種規則我們稱為是“強制實現”。

    virtual關鍵字標識的方法叫虛方法,從含義上我們可以認為虛方法是一個允許其子類重新定義的方法。

  抽象方法與虛方法最大的區別是前者是強制實現,而後者是允許其有選擇的重新實現,不實現也可以。

再一個區別就是擁有抽象方法的類不可以實例化,而擁有虛方法的類不受此限制。

 

四:結束

   從有寫這篇文章的想法到今天寫完,前前後後差不多有一個星期的時間,在寫這篇文章的過程中,充分體會到自己表達能力和描述能力的不足,有時一個簡單的概念,卻不知如何將其清晰的表現成文字。其中的感受,無法細述,但如果通過這篇文章,有些同事能有點滴的收獲的話,那就是對我最大的鼓勵! 當然,如果有什麼疑惑或不同的見解,更是歡迎討論和交流!

 

五:參考資料

    1:阎宏,《Java与模式》,电子工业出版社

    2:博客園網站: www.cnblogs.net