如何設計類,可以使得文件間的編譯依存關係降至最低

方法一:使用pimpl idiom(pointer to implementation)的類,常被稱爲 Handle classes.
將類分割爲兩個類,一個只提供接口,另一個負責實現該接口。分離的關鍵在於以“聲明的依存性”替換“定義的依存性”。

以Person類爲例,假設負責實現的那個所謂implementation class取名爲PersonImpl, Person將定義如下:

#include<string>    //標準程序庫使用預編譯頭文件。
#include<memory>    //爲了智能指針tr1::shared_ptr而含入

class PersonImpl;   //Person實現類的前置聲明
class Date;         //Person接口用到的class的前置聲明
class Address;

class Person{
public:
	Person(const std::string& name,const Date& birtthday,const Address& addr);
	std::string name() const;
	std::string birthDate() const;
	std::string address() const;
private:
	std::tr1::shared_ptr<PersonImpl> pImpl; //指針,指向實現物;
}
這裏Person類中只內含一個指針成員,指向其實現類。這種設計常被稱爲pimpl idiom(指針實現)。
這樣的設計下,Person的客戶就完全與Date,Address以及PersonImpl分離了。這些類的任何修改都不需要Person客戶端重新編譯。
編譯依存性最小化的本質就是:讓頭文件能夠自我滿足,萬一做不到,再讓它與其他文件內的聲明式相依。
真正用的時候,將所有的函數轉交給相應的實現類,並由實現類完成實際工作。

下面是Person類的兩個成員函數的實現:

#include "Person.h"   //這裏需要包含Person class定義式。
#include "PersonImpl.h"  //也必須包含PersonImpl class定義式。

Person::Person(const std::string& name, const Date& birthday, const Address& addr)
:pImpl(new PersonImpl(name,birthday,addr))
{}

std::string Person::name() const
{
	return pImpl->name;
}

在上方成員函數實現中,Person構造函數以new調用PersonImpl構造函數,

在Person::name()函數內調用PersonImpl::name。這種設計方法就將類的聲明和實現相分離,從而最大化的減小了編譯依存關係。

對於Handle classess這種方法,成員函數必須通過指針實現取得對象數據。這種形式會爲每一次訪問增加一層間接性。

而每一個對象消耗的內存數量必須增加實現指針PersonImpl的大小。並且,這個實現指針PersonImpl必須在Handle classe構造函數內初始化,並指向一個動態分配得來的實現對象。所以這種操作方法要將蒙受因動態內存分配而帶來的額外開銷以及bad_alloc異常(內存不足)的可能性。

方法二:令Person成爲一個特殊的抽象基類。稱爲Interface class。

這種類的目的是詳細描述派生類的接口,因此它通常不帶成員變量,也沒有構造函數,只有一個虛析構函數以及一組純虛函數,用來敘述整個接口。

Interface class爲這種class創建新對象會採用工廠函數或虛構造函數來實現。這些函數返回指針(通常爲智能指針),

指向動態分配所得的對象,而該對象支持Interface class的接口。這樣的函數往往在Interface class內被聲明爲static。

實現代碼如下:

class Person{
public:
	virtual ~Person();
	virtual std::string name() const = 0;
	virtual std::string birthDate() const = 0;
	virtual std::string address() const = 0;

	static std::tr1::shared_ptr<Person>   //返回一個tr1::shared_ptr,指向一個新的Person,並以給定之參數初始化。
		create(const std::string& name,
			const Date& birthday,
			const Address& addr);

};

可以這樣使用它們:、

std::string name;
Date dateOfBirth;
Address address;

//創建一個對象,支持Person接口。
std::tr1::shared_ptr<Person> pp(Person::create(name,dateOfBirth,address));

std::cout<<pp->name()<<pp->birthDate()<<pp->address();

實現Interface class有兩個常見機制,這裏採用的是從Interface class(Person)繼承接口規格,然後實現出接口所覆蓋的函數。

當然,支持Interface class接口的那個具象類必須被定義出來,而且真正的構造函數必須被調用。

假設Interface class Person有個具象的派生類 RealPerson,後者提供繼承而來的虛函數的實現:

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

有了RealPerson之後,寫出Person::create的實現代碼:

Person::create(const std::string& name, const Date& birthday, const Address& addr)
{
	return std::tr1::shared_ptr<Person>(new RealPerson(name,birthday,addr));
}



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