1. 程式人生 > >[C++] 類的"實現"與"介面"分離

[C++] 類的"實現"與"介面"分離

C++這門語言囊括了多種語言正規化,並不是嚴格的OOP語言,所以在“實現與介面的分離”這一方面做得並不算好。 這裡簡述C++的類設計中把實現與介面分離的方法.

在C++的某個class的定義中,不僅聲明瞭介面,還可以看到實現的具體細節,當然這個視角是針對類內部的,通常是在私有域private中,比如下面這個類:

class Person {
public:
Person(const std::string& name, cpnst Date& birthday,
const Address& addr);
std::string name() const
; std::string birthDate() const; std::string address() const; private: //實現的細節 std::string theName; Date theB工rthDate; Address theAddress; };


要實現這麼一個類,一般的做法就是在所在宣告的標頭檔案中include DateAddress兩個類的宣告檔案include"date.h"/include"address.h",如果Date或者Address類發生了改動,那麼連帶Person類及包含Person標頭檔案的後續檔案都要重新編譯。這樣子的話Person

類的使用者就會有極大的不便,Person/Date/Address中的任何一個類改動,Person客戶端都必須重新編譯。

我們可以借鑑類似Java中的做法,宣告一個類時,出現的是一個指向該型別的指標,只要分配給該指標足夠的空間,我們就可以隱藏其“實現”了。我打一個可能不是那麼形象的例子,原始版本就是一本書,我們是這本書的使用者,書中的內容有改動時,出版社就需要重新印刷一份,現在我們不用拿著書了,我們手中只有一個目錄,我們需要資料的時候直接按照目錄向出版社拿內容,出版社可以隨時更新他手頭的資料內容,而我們只要專注於“索要”資料就行了。

但是這樣子分離的還不夠徹底,我們把Person類分離為兩部分,純粹的兩部分,一部分叫實現類

(定義為PersonImpl [Person Implementation]),另一部分叫介面類(即 Person)。
把具體的實現放在PersonImpl類中,然後通過Person類管理指標形式的PersonImpl物件,然後把介面暴露給客戶,實現分離。

#include <string>
#include <memory>

class Personlmpl;  //實現類的宣告
class Date;     //介面中需要用到的其他類的宣告
class Address;

class Person {
public:
Person(const std::string& name, const Date& birthday,
const Address& addr);
std::string name() const;
std::string birthDate() const;
std::string address() const;
private:
std::shared_ptr<PersonImpl> plmpl;  //指標的形式,且遵循以類管理資源的原則
};

在PersonImpl類和Person類中,有著同名的成員函式,介面也是完全一樣的
這樣的設計,Person類的使用者就完全的與Person/Date/Address三種類的實現細節分離開了,只要專注於介面的使用就OK了。
並且在這樣的設計下,如果Date,Address之類的Class有變動,Person類的使用者也不需要重新編譯。

另一種方法也許更常見,就是把介面(Interface)都在一個介面類(抽象類)中描述,然後由它的派生類來具體實現,有著純虛擬函式的類就是抽象類,抽象類是一種不能例項化的特殊類,所以經常可以用來描述介面:

class Person {
public:
virtual -Person();
virtual std::string name() const = 0;  // =0字尾,即為純虛擬函式
virtual std::string birthDate() canst = 0;
virtual std::string address() const = 0;
};

正常的使用就是在派生類中具體實現:

class RealPerson: public Person {
public:
RealPerson(const std::string& name, const Date& birthday,
const Address& addr)
theName(name) , theBirthDate(birthday) , theAddress(addr)
{}
virtual -RealPerson() { )
std::string name() const;
std::string birthDate() const;
std::string address() const;
private:
std::string theName;
Date theBirthDate;
Address theAddress;
}

除此之外,抽象類也是“工廠設計模式”的一種比較優良的實踐,我們可以通過一個在抽象類中定義一個create函式(通常是靜態方法,用static關鍵字來修飾),用來動態生成繼承體系中的各種例項,都可以通過一個create函式來實現,當然別忘了,為了滿足多型的要求,返回的應該是一個指標(*)或引用(&)

class Person {
public:
static std::shared_ptr<Person> create(const std::string& name, const Date& birthday, const Address& addr)
{
return std::shared_ptr<Person>(new RealPerson(name, birthday,addr);  //此例中只展示了生成RealPerson例項,更真實的實踐中通常取決於引數,環境等等
}

};

當然兩種方法都是很好很實用的剝離“實現”與“介面”的實踐,減少類與類之間的耦合度。
他們分別叫做handle classinterface class,可以從名字形象的推測出它們的含義。