C++語言實際上是幾種不同語言的聚集地,你可以把他看成一種語言,也可以把他看成一種語言,要進行對C++程序地開發,那麼復雜性會大幅度地增加,這就是C++在實踐中難於控制的一個主要原因。
混合使用不同風格,就好像在一個源文件裡混合使用多種不同的語言,復雜和不一致性必然暴露。當然,C++獨特的魅力正在於混合風格編程的強大威力。這正是一把雙刃劍,雖然具有潛在的強大威力,但是通常來說也是導致項目混亂的重要原因。
我認為以下面的原則進行實際開發,將可以在一定程度上規避風險:
1). 在任何一個單個的時間點,只使用一種編程風格。
2). 以一種風格為主風格,用它來組織整體模塊的開發。
3). 在遇到特別適合另一種風格的典型場景,可以用一個子模塊包裝該場景,然後在該子模塊中使用該風格,但記住遵循要求1,避免混合風格。此外,必須通過封裝手段將該模塊包裝起來,以符合主體風格的要求。比如說,主風格是better C,在某個子模塊中用到了面向對象,則應當使這個子模塊從整體上看來像是一個普通的C過程。
4). 在個別場合,混合風格的確有很大的好處。但是,這種情形是比較少見的,一般來說比較成功的實踐已經總結成patterns,所以在工程實際中,可以強行規定,只有在符合某個patterns的情況下才可以謹慎地使用混合風格,嚴禁擅自創造新的混合用法。
現在來討論一下究竟應當如何劃分C++風格。Stroustrup對C++風格的分類是從語言開發者的角度進行的。如果我們以下面的原則進行分類,我認為會得出不同的結果:
1) 每一種風格必須構成一個完整的子語言,具有完備性,可以單獨使用這一子語言開發任何軟件系統,有經過歷史驗證的成功經驗。
2) 每一種風格必須相對簡單,有一致的、簡單的、得到認可和驗證的原則。
3) 每一種子語言必須能夠在現實世界中找到相對應的其他語言。
依據以上原則,我將C++語言劃分為三個半子語言:
1) Better C, 只增加函數重載、引用類型、缺省參數等簡單特性的類C子集。對應ANSI C語言。
2) ADT C++,即C with Class,整個程序由平面化的具體類(concrete class)對象構成,無繼承,無多態。對應Ada 83語言。
3) IDL C++,我稱之為Interface-Oriented,典型范例是COM組件模型。
3.5) GP C++, 利用模板技術形成了一種庫和組件的實現語言。這不是一種完整子語言,一方面因為可以把它看成是ADT C++的一種延伸,另一方面它必須依附於其他風格而發揮作用。
顯然,我這裡遺留了一個最重要的風格,也就是我們通常所說的“傳統面向對象”風格,由Smalltalk,Java等語言所展示的。由MFC等類庫經過多年實踐論證了的一種風格:靠龐大的繼承樹抽象和組織各種數據類型,靠繼承和組合實現代碼復用。這種風格為什麼沒有被我提及呢?
因為我認為這種風格實際上是一種混合風格!可以認為是在試圖融合上述第2、3和3.5種風格。在前述的三條原則裡,它嚴重地違背了第二條。由於C++的靜態本質,由於C++缺乏天然的類庫和垃圾收集機制,使得在C++語言中進行Smalltalk風格的編程非常非常困難,以至於為了克服這些困難,C++語言實際上發展出了一套不同於Smalltalk、Java風格的獨特的“面向對象”編程風格。
這套風格歷經近15年實踐,應該說有成功有失敗,雖然出版了大量的著作,至今沒有形成簡單的、一致的、可仿效的風格指導。從某種意義上說,如此多的C++面向對象編程指導書籍十幾年常盛不衰,恰恰說明這種風格的困難程度和難以仿效性。
就我個人而言,我已經不再以這種風格為指導思想了。我不會再拼命地構造繼承樹,思考哪些函數應該是虛函數這類問題了。你可以認為“為了復用代碼而進行的繼承”是這種風格的標志。
請注意,ADT C++允許組合,對於繼承則應該想盡一切辦法避免。而IDL C++的典型代表COM,根本就不支持這種繼承,它支持的只是接口的復用。當然,這並不是要否定十幾年來C++語言在面向對象方面發展的成績。但是,如果你現在從頭開始規劃一個完整的項目,那麼我認為如果選擇這種雜合風格,是不太明智的。但是這種風格也有兩個典型的使用場景:
1) 有一個完整的框架支持。比如MFC。雖然這種風格本身有很多技術難點,但是MFC這樣的框架已經幫你克服了一部分,給你營造了一個類似Smalltalk那樣的、相對舒適環境,這時候可以使用這種風格。但是通常要認識到,這類框架在克服不少技術難點的同時,引入了一些新的問題,有時是更加難以對付的問題,所以要明智,並且做好充分准備。
2) 符合經典模式。如果遇到某個典型的“面向對象”場景,已經有了成熟的、優秀的、現成的、文檔化了的設計解決方案,則可以有選擇的、謹慎地使用之。我指的主要就是GoF和其他一些設計模
這裡所謂的“經典模式”數量絕對不會太多,但是卻大量地、反復地出現在設計中,並且往往復合出現。這樣的情況用已經經過驗證的設計方案來解決是非常合適我個人在這裡有一些實踐,覺得應該注意幾個問題。第一是要謹慎,我遇到過大量的情形,看上去很適合用某個模式來解決,但是真的用了才發現並不是這麼回
在不適合的地方套用了錯誤的模式,會把事情弄得一團糟;二是最好將設計方案局部化,包裝起來,從外面看不出你使用了什麼模式。三是注意內存問題。使用OO風格的最大障礙其實就是內存問題。