對於c++面向對象的深刻認識和理解--哲學角度看問題(源生論)

   對象論認爲:數據和邏輯不是分離的,而是相互依存的。相關的數據和邏輯形成個體,這些個體叫做對象(Object),世界就是由一個個對象組成的。對象具有相對獨立性,對外提供一定的服務。所謂世界的演進,是在某個“初始作用力”作用下,對象間通過相互調用而完成的交互;在沒有初始作用力下,對象保持靜止。這些交互並不是完全預定義的,不一定有嚴格的因果關係,對象間交互是“偶然的”,對象間聯繫是“暫時的”。世界就是由各色對象組成,然後在初始作用力下,對象間的交互完成了世界的演進。

 

開放-關閉原則(OCP):軟件實體應該可以擴展,但不可以修改。

      爲什麼忽然扯到OCP呢?因爲,OCP正是上文討論的哲學原理在程序世界的具體表述。我們來對比看一下,到底OCP是個什麼意思。
      還是上面看病那個例子,什麼叫可以擴展?就是說,因爲在某個抽象層次是進行表述,就不能把話說死了,不能全是這個、那個的把每個對象都指派明白。如,那句話改成“我的右腳扭到了,要去北京航空航天大學醫院去看胡青牛醫生”,這句話就沒有擴展性可言了,所有話都說死了,你如果去的是北醫三院或臨沂市人民醫院,那麼語義就不對了,而如果找的不是胡青牛而是華佗或扁鵲,語義也不對了。爲什麼無法擴展?因爲所有點都指定了具體的對象。
      而原話“我生病了,要去醫院看醫生”則擴展性很大,因爲只要不違反可映射性定義,映射到任何符合條件的對象都正確。擴展性和靈活性大大提高了。所以,“可以擴展”四字從哲學上其實是要我們在設計和開發軟件時提高抽象層次,不要總在具體對象層面上進行處理。這下,你明白爲什麼說OCP可以提高軟件的可擴展性和靈活性了吧。
      再來說說“不可以修改”,因爲如果隨便亂改,那就天下大亂了。還是醫院那個例子,“醫院”這個類所映射到的對象,一定是治病的地方。如果這東西隨便改,例如明天“醫院”和“食堂”的概念對換了,那麻煩了,我們所有人都要改,要把兩個概念從腦子中對換過來,全世界的書、報紙、Internet……凡是依賴這兩者進行表述的地方都要改,那不是天下大亂麼?軟件世界中也會發生這種牽一髮而動全身的問題。所以我們提倡設計好的類一定要“對修改關閉”。
      以上,就是OCP的哲學意義。

里氏代換原則(LSP):子類型應該能代替掉其父類型,且代替後程序運行情況不會錯亂。

      我們還是用例子去理解LSP
      現代辦公幾乎都要用到個人計算機,個人計算機本身是一個抽象概念,臺式PC是其中一個子類。後來,發明了筆記本電腦,我們想把筆記本電腦歸爲個人計算機的子類,是否合理呢?根據LSP,我們將臺式PC都替換成筆記本電腦,世界應該是照常運行的(當然,實際情況可能複雜些,有些地方不能用筆記本電腦替換,但這裏我們忽略這種差別)。我們辦公時依賴的類是“個人計算機”,而筆記本電腦完全可以替代這個類型而使得世界運行正常,所以,我們說將筆記本電腦歸於個人計算機的子類是符合LSP的。
      後來,又發明了轉基因黃瓜,我們也想將它歸到個人計算機的子類中去,行不行呢?好的,現在我們再運用LSP,將世界上每個依賴個人計算機的地方都替換成一根轉基因黃瓜。好的,世界人民都瘋了!明顯這種替換會令世界運行錯亂。所以,我們不能讓轉基因黃瓜繼承個人計算機。
      上面的例子是顯而易見的,但有些卻不那麼明顯。例如,現在問,獸醫是醫生的子類嗎?這個問題,一下子還真不是很好回答,但我們可以LSP一下,現在,我們把醫院裏的醫生都替換爲獸醫,你還敢去醫院看病嗎?嗯,這下子不用我多說了吧。
      最後一定要說明的是,LSP應用於程序世界和現實世界時有很大差別的,現實世界繁雜、不確定性因素多,而程序世界簡單、確定。總之,LSP就是讓你記住一條,凡是系統中有繼承關係的地方,子類型一定能代替父類型,而且替換後程序運行要正常。換言之,繼承是一種嚴格的“IS-A”關係,也是“一般和特殊”的哲學原理在程序世界中的體現。

 

繼承的話題就討論到這裏了。很多朋友在運用繼承時有疑惑,或不能很好的確定繼承關係,歸其根本是沒有真正理解繼承的意義。只要能理解繼承的本質意義,加上OCPLSP的運用,是可以寫出正確的繼承體系。

泛化耦合(Generalization Couple):由於泛化(繼承)關係的存在,在兩個有祖孫、父子關係的類間形成的一種邏輯關聯。

      然後,我們討論另一種耦合。
      在文章開始,我們說對象論將對象看做基本元素,而對象中有數據和方法。在現實世界中,數據並不總是簡單數據。客觀存在一些對象,它們的數據是另一個或另一些對象。例如,一個具體的羊羣,有一項數據是很多具體的羊。其中羊也是對象。當抽象成抽象的“羊羣”和“羊”類的時候,這種包含關係也隨之被抽象到了類中,由此在兩個類之間就形成了耦合。
      這種耦合出現的哲學基礎是,對象本身固有的包含關係,在進行事物抽象時被同時抽象到了類中。所以,我個人將其稱爲包含耦合。
      包含耦合又分爲兩種情況,一種是被包含對象單純聚合在包含對象中,但沒有形成哲學意義上“整體與部分”的關係,這是一種相對較弱的聯繫,叫做聚合。例如,上例中羊羣和羊就是聚合關係,如果拿掉一兩隻羊,羊羣還是羊羣。

      聚合(Aggregation):一種弱的擁有關係,體現A對象可以包含B對象,但B對象不是A對象的一部分。

      另一種情況是,被包含對象和包含對象形成了哲學意義上“整體與部分”的關係,如汽車和輪子,把輪子拿掉,汽車就不再是完整意義上的汽車了。這種關係叫做組合。

      組合(Composition):一種強的擁有關係,體現了嚴格的部分和整體的關係,部分和整體具有一樣的生命週期。

      通過上面的探討,我們認識了泛化耦合、聚合和組合三種耦合形式,最後,還有一種耦合叫依賴。什麼是依賴呢?我們知道,在對象論中,將世界的演進看成是在初始作用力下,對象之間相互調用、相互協作完成的。如果兩個類在需求範圍內,既定邏輯上存在協作的可能,那麼這兩個類就存在依賴關係(或叫關聯關係)。其實,我們常說的“低耦合,高內聚”、“降低耦合”等建議,主要是針對依賴說的。

      依賴(Dependency):由於邏輯上相互協作可能,而形成的一種關係。

      好的,到目前爲止,我們已經認識了四種基本耦合。下面用一副圖,直觀感受一下世界的各種耦合。

5.1、耦合示例

      圖5.1展示了幾種耦合的示例。其中汽車和交通工具屬於泛化耦合,輪子和方向盤組合於汽車,汽車聚合成車隊,而汽車和司機具有依賴關係。這幅圖只是耦合的一個小片段,實際上,世界上各種對象形成了一張複雜的耦合網,正因爲有耦合的存在,世界才能演進。正如馬克思主義哲學所說:聯繫是普遍的、客觀的。所以,耦合的存在,有其深刻的哲學意義。

程序世界裏的對象沒有選擇權。

      爲什麼會這樣?因爲如果對象有選擇權,就沒法貫徹OCP了!你要是活在程序世界裏,不但給你包辦婚姻,連吃飯、上學……一切的一切,你都得服從包辦,對象一點點選擇權也沒有。至於誰給你包辦的,那是後話。
      看了這些,你還敢去程序世界嗎?不過這還不是最恐怖的,告訴你更恐怖的一點:

      程序世界裏的對象不認識對象。

      沒錯,良好的面向對象提倡對象不認識對象!很不可思議?其實,這就是所謂的“低耦合”,我們喊了那麼多年的“低耦合”,到底什麼是低耦合?所謂低耦合,就是先剝奪對象的選擇權,再剝奪對象的感覺。對象間誰也不認識誰,只知道對象能提供什麼服務。

何東西用得好不好,不在於是不是熟練掌握用法,而在於是不是用對了地方。而要想用對地方,就要弄清楚這個東西的“怎麼出來的”和“出來是做什麼用的”。

類與接口的不同點有以下幾點:
      I. 抽象範疇不同。類是對象“體徵”的抽象,接口是對象行爲的抽象。
      II. 抽象動機不同。抽象出類是爲了幫助記憶、認識世界,抽象出接口是爲了實現低耦合交互。
      III. 關注不同。類關注共同的體徵,接口關注用來交互的行爲。
      IV. 存在範疇不同。類存在於抽象層次樹上,接口存在於接口網。
      V. 應用範疇不同。類應用於結構範疇,是靜態概念,接口應用於運作範疇,是動態概念。

接口的哲學意義:對客戶類的保證,對服務類的約束。

      正是接口約束了服務類必須實現什麼功能,客戶類纔可以在不知道具體服務類的情況下“放心”進行交互,因爲接口對客戶類提供了一種保證。希望各位能好好體會接口的這種哲學意義,這對於對象論的良好運行體質的理解非常重要。
      可是,這樣還不夠,我們還有一個非常重要的問題沒有討論:誰有權利定義接口?或者說服務類和客戶類誰擁有接口?當然,理論上時誰擁有都可以,但卻會對世界的運作產生巨大影響。我們先看服務類擁有接口的情形。

6.4、服務類擁有接口

      如圖6.4,由於服務類擁有制定接口的權利,所以各個服務類都定義了自己的接口,一般情況下他們的接口是不相容的。如圖,司機可以駕駛汽車,但由於輪船、飛機各自有自己的可駕駛接口,所以會開汽車未必會開飛機和輪船,如果要開飛機或輪船還要一個個學,現實世界中就是這樣一種情況。所以,這種世界的運行其實接口幾乎沒有起到作用,由於服務類是“大爺”,所以它們可以指定諸多霸王條款,而客戶必須忍氣吞聲去遷就,所以,實際的依賴方向還是從客戶類到服務類。
      下面在看看客戶類擁有接口會是什麼樣子。

6.5、客戶類擁有接口

      看上圖,客戶終於翻身做主人了,現在客戶擁有定義接口的權利,服務類必須無條件實現,這下好了,只要會開汽車,就會開輪船和飛機,因爲客戶有權利定義一個統一的接口,服務類必須無條件實現!這樣,三種交通工具的駕駛方法必須完全一致(雖然現實世界還沒有這樣),這回客戶終於可以揚眉吐氣,體會一把“顧客是上帝”的感覺了。
      在圖6.5的情況下,司機可以有權定義接口,他不必“知道”服務類,而服務類必須“知道”客戶定義了什麼接口,你有沒有發現,依賴的方向已經悄悄倒置過來了!變成服務類依賴客戶類了(誰知道誰,誰就依賴誰)!這就是“依賴倒置”的由來。不必說,所謂依賴倒置原則就是讓我們必須按圖6.5的方式運行世界,而不能按圖6.26.36.4的方式。下面正式定義依賴倒置原則。

      依賴倒置原則(DIP):客戶類和服務類都應該依賴於抽象(接口),並且客戶類擁有接口。

      我想,看過上述來龍去脈,已經不用我再去解釋這個原則了吧。

程序世界是有這麼一個統治者,他就是大名鼎鼎的“依賴注入容器(DI)”,也有人叫做“控制反轉容器(IoC)”。      什麼叫依賴注入?什麼叫控制反轉?如果你看了上面的文章,那太好理解了,依賴注入就是容器挑選符合接口的服務類爲客戶類提供服務。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章