[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
Date
和Address
兩個類的宣告檔案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 class
和interface class
,可以從名字形象的推測出它們的含義。