近期重新看了一下C++,一是感覺清晰了許多,二是覺得若是換個角度看的話,會有不一樣的體會,并且也容易記住C++中的一些特性。本文就試圖將集合論中的相關知識引入到C++的封裝、繼承、多態(tài)上,讓我們對它有個新的認識。
一、封裝
C語言中,代碼之間的關系都是函數(shù)式的調用。這里面牽扯到對數(shù)據(jù)的操作,若操作的都是局部變量,那一切都太平了。但若是幾個函數(shù)操作同一個非局部變量,考慮到模塊化,那么就要將變量和操作變量的函數(shù)整合在一起,這就是C++中的封裝。
C++里面引入了class的概念,目的是封裝數(shù)據(jù)和數(shù)據(jù)上的操作,使其成為一個獨立的模塊。若是將這個獨立的模塊(代碼和數(shù)據(jù))想象成集合,那個class A的集合為:
此時若再引入一個class B,則有下面四種可能性,情況三、四實際上類似。
情況一,只需要封裝就足夠了。處理情況二、三、四時,為了考慮代碼共享,需要引入繼承機制。
二、繼承
我們先考慮情況二,由于A和B有公共代碼(成員函數(shù)或者是成員變量),故通常考慮將公共的部分定義為class C,然后由A、B去繼承它。
對于情況三、四,我們不需要演變,直接讓A繼承B,或者B繼承A即可。
若此時引入class D,那么情況就會復雜很多。簡單起見,以情況二為擴展,考慮添加class D后的某一種。后續(xù)你會發(fā)現(xiàn),情況三、四類似。
此時,最合理的方式是引入四個類,class E, class F, class G, class H。E為基類,F(xiàn)、G、H為一級子類、A、B、D為二級子類。
但是,這種解決方案有問題:
1.若是再添加class I,class J,那復雜度就可想而知了。
2.雖然代碼冗余是消除了,但是引入了四個類,也著實有點多,更嚴重的話會導致“類泛濫”。
為了能統(tǒng)一解決添加的類D,我們將圖四拆分成D和A,以及D和B的關系。這樣就轉化為圖2中的一種:情況二。
圖6中,class H表示D和A的公共部分,class G表示D和B的公共部分。此種解法雖然有代碼冗余,但簡單了許多,事實上,我們很多時候處理類,就是這么處理的。
在這種情況下,若是添加class I,class J,都可以轉化為新添加類和已有類之間的單獨關系,即圖2中的四種情況。
同時,也可以發(fā)現(xiàn),我們無法在類的繼承結構中完全消除代碼冗余,原因是多個類的情況下,實在是比較復雜。
當我們在使用這些包含繼承結構的類的時候,考慮圖2的情況三,若B繼承自A,那么實際上B也可以當A用的,這很好理解,本來A就是B的一部分。但若是想讓A代表B呢(實際上就是B對象,只是用的時候當A用),為了完美解決這個問題,就要引入多態(tài)了。
三、多態(tài)
由前面的分析可知,類之間的關系都可以簡化為圖2的情況。圖2的情況三中,A當B用(實際上只有B對象)又分為以下三種情況。第三種情況有點別扭,可能是需求決定的吧。
1.使用B中的A部分。直接使用A操作即可。
2.使用B中的非A部分。需要將A轉化為B才可使用。
3.B覆蓋定義A的公共接口或者成員變量。當B作為A使用的時候,A中的公共接口或者成員變量是在非A中的,實現(xiàn)這一機制的就是多態(tài)。
C++中,基類定義虛函數(shù),子類可以重新實現(xiàn)它,以實現(xiàn)多態(tài)。令人奇怪的是,沒有虛成員變量的概念,我覺得可能有以下幾個原因:
1.沒必要提供虛成員變量。父類的成員變量屬于存儲空間,可以直接用。不像函數(shù),屬于代碼無法直接替換。
2.可能編譯器要實現(xiàn)這個會比較復雜吧。
3.封裝的概念是少暴露成員變量,只暴露接口。因此,好的類的設計是沒有公共的成員變量的,也就不存在虛成員變量一說了。但是,從完整性的角度而言,應該提供虛成員變量的。