天天看點

《C++程式設計風格(修訂版)》——3.5 接口與實作

本節書摘來自異步社群出版社《c++程式設計風格(修訂版)》一書中的第3章,第3.5節,作者:【美】tom cargill,更多章節内容可以通路雲栖社群“異步社群”公衆号檢視。

c++程式設計風格(修訂版)

為什麼要使用繼承?如果對繼承關系作進一步的分析,我們會發現程式中的繼承其實可以完全去掉。是以,第二種解決方案就是使用成員對象而不是繼承。

在第2章中已經讨論過,一個c++類有着兩個重要的方面:用于描述類行為的公有接口,以及行為的私有實作。大多數繼承所采用的都是公有繼承的形式:派生類同時繼承了基類的接口和實作。不過,我們還可以有選擇性地進行繼承,即派生類可以隻繼承接口或者隻繼承實作。在私有基類中,派生類繼承了所有的實作,但沒有繼承任何接口。而在繼承公有的抽象基類時,派生類繼承了所有的接口,但所繼承的實作可能是不完整的或者不存在的。

我們需要對每一個繼承都進行謹慎的評估。例如,究竟是需要繼承接口,還是需要繼承實作,抑或是對二者都需要進行繼承。在本程式中,charstack和intstack僅需要繼承實作。雖然charstack的接口類似于intstack的接口,但二者還是不同的。雖然它們各自的成員函數都有着相同的名字,但其中一個成員函數的參數是char類型的值,而另一個成員函數的參數是int類型的值。charstack和intstack并沒有共同的接口;客戶代碼既不能把它們各自的對象進行統一處理,也不能交換使用。charstack和intstack的抽象都堆棧抽象的特化,但它們并不是stackindex抽象的特化,intstack對象并不是一種stackindex對象。從客戶代碼的角度來看,stackindex、intstack和charstack提供的是不同的服務。stackindex處理的是索引,intstack管理的是一個整數值的堆棧,而charstack管理的是一個字元堆棧。從客戶代碼的角度來看,這種“是一種(is kind of a)”的關系是沒有意義的,是以在這些類中的公有繼承就是不合适的。

派生類與其私有基類之間的關系其實類似于客戶類與伺服器類的關系。将stackindex作為一個私有基類,并将intstack和charstack從stackindex繼承下來的主要目的,是為了使用stackindex的索引管理服務,是以派生類可以被看作是stackindex的客戶。我們還有另外一種不用繼承來表達這種關系的方法,那就是在intstack和charstack的對象中,分别使用一個stackindex執行個體作為私有成員。

現在,stackindex是charstack和intstack的一個私有成員對象,而并不是被作為基類。如程式清單3.3所示,我們對程式結構的改動是非常小的,同樣類型的對象依然提供着同樣的服務。差別僅在于現在提供服務的是成員對象,而并非私有基類,是以我們通過成員變量的名字來使用伺服器類,而不是類的名字。在本例中,選擇私有基類的形式與選擇私有成員對象的形式相比,隻是一種個人喜好——這兩種方法在功能上是完全等價的。不過,為了簡單起見,成員對象通常是一種更好的選擇,因為與繼承相比,成員對象的語義要更加清晰。

識别出對實作的繼承;可以使用私有基類或者(更好的方法是)使用成員對象。

程式清單3.3 用成員對象代替繼承

《C++程式設計風格(修訂版)》——3.5 接口與實作
《C++程式設計風格(修訂版)》——3.5 接口與實作
《C++程式設計風格(修訂版)》——3.5 接口與實作

重載與預設參數

在結束這個示例之前,我們要注意在charstack和intstack中重載構造函數的相似性。在第2章讨論string類的重載構造函數時,就提到過應該使用預設參數的形式來代替函數重載的形式,如程式清單3.4中的charstack所示。對于每個類來說,将多個構造函數合為一個構造函數可以簡化代碼的維護工作。雖然在有些時候,将函數重載的形式轉換為預設參數的形式并不是很直覺的,但我們應該盡可能地去考慮這種轉換。我們來回顧一下以下這條規則:

考慮使用預設參數的形式來代替函數重載的形式。

最後,注意在程式清單3.4的charstack::charstack中,循環在每次疊代時仍然調用了函數strlen()。如果這個構造函數被證明是一個性能瓶頸,那麼可以很容易改正這個問題。

程式清單3.4 用預設參數來代替函數重載

《C++程式設計風格(修訂版)》——3.5 接口與實作

本文僅用于學習和交流目的,不代表異步社群觀點。非商業轉載請注明作譯者、出處,并保留本文的原始連結。

繼續閱讀