1. 程式人生 > >區分介面繼承和實現繼承

區分介面繼承和實現繼承

公有繼承public的概念,可以分為兩部分:函式介面繼承和函式實現繼承。

在public繼承下,派生類總會繼承基類的介面。也就是成員函式的介面總是會被繼承。

pure virtual純虛擬函式只具體指定介面繼承。

impure virtual(非純)函式具體指定介面繼承及預設實現繼承。

non-virtual(普通)函式具體指定介面繼承及強制性實現繼承。

例子1:一考慮一個展現繪圖程式中各種幾何形狀的class繼承體系。用來說明純虛擬函式只具體指定介面繼承。

class Shape{
public:
	virtual void draw() const = 0;
	virtual void error(const std::string& msg);
	int objectID() const;
};

class Rectangle: public Shape{...};
class Ellipse: public Shape{...};
Shape是一個抽象類,它的純虛擬函式draw使它成為一個抽象類。所以不能夠建立Shape類的實體,只能建立其派生類的實體。
SHape影響了所有以public形式繼承它的派生類。因為public繼承意味著派生類是基類的一種關係。對基類為真的任何事情一定也對其派生類為真。
如果某個函式可施行於某類身上,也一定可以施行於其派生類身上。所以在public繼承下,派生類總是繼承基類的介面。成員函式的介面總是會被繼承。

在Shape類中,目前聲明瞭三種形式的成員函式,且都是public成員函式。

第一種形式:純虛成員函式pure virtual 

class Shape{
public:
	virtual void draw() const = 0;
};
純虛擬函式有兩個突出特性:必須被任何“繼承了它們”的具象類重新宣告,並且它們在抽象類中通常沒有定義。
因而在基類中宣告一個純虛擬函式的目的是為了讓派生類只繼承函式介面。也就是說對具象類設計時,必須提供這個draw函式介面,但是具體怎麼實現抽象類不干涉。

可以為純虛擬函式Shape::draw供應一份實現程式碼,但呼叫時必須明確指出類的名稱。

Shape* ps = new Shape;  //不能夠建立抽象類的實體,這句話錯誤
Shape* ps1 = new Rectangle; //可以建立抽象類的派生類的實體
ps1->draw();  //呼叫Rectangle::draw
Shape* ps2 = new Ellipse;
ps2->draw();  //呼叫Ellipse::draw
ps1->Shape::draw();  //呼叫抽象類中的純虛擬函式,必須明確指出抽象類的名稱。
ps2->Shape::draw();
成員函式宣告為非純虛擬函式的目的是讓派生類繼承該函式的介面和預設實現。

例子2:考慮某航空公司設計的飛機繼承體系,該公司只有A型和B型兩種飛機,兩者都以相同方式飛行。因而可以設計出如下的繼承體系;

class Airport{...};   
class Airplane{
public:
	virtual void fly(const Airport& destination); //非純虛擬函式
}

void Airplane::fly(const Airport& destination)
{
	//預設程式碼,將飛機飛至指定目的地
}

class ModelA:public Airplane{...};
class ModelB:public Airplane{...};
這裡把fly宣告為虛擬函式的原因是,所有飛機都會飛,但是不同型飛機原則上需要不同的fly實現函式。
為避免在A型和B型的fly函式中撰寫相同程式碼,預設飛行行為由基類Airplane::fly提供,然後再被A型和B型繼承。

這裡Airplane類的設計體現了面向物件設計的思想。兩個派生類共享一份相同性質,所有共同性質由基類提供,然後被兩個派生類繼承。這種面向物件的設計,避免程式碼重複,並提升未來的強化能力,減緩長期維護所需的成本。

如果該機場新增新式C型飛機,但是C型飛機的飛行方式與A型和B型不同。
有兩種解決辦法:一是在基類中以不同的函式分別提供介面和預設實現。二是藉助純虛擬函式也可以擁有自己的實現函式,將預設飛行行為寫在純虛擬函式的實現中。

首先看第一種做法:用不同的函式分別提供介面和預設實現。

class Airplane{
public:
	virtual void fly(const Airport& destination) = 0;  //採用純虛擬函式只提供介面
	
protected:
	void defaultFly(const Airport& destination)
	{
		//預設行為。並且訪問方式是protected。
	}
};

派生類若想使用預設實現,可以在其派生類fly函式中對defaultFly做一個inline呼叫。

class ModelA: public Airplane{
public:
	virtual void fly(const Airport& destination)
	{defaultFly(destination);}
};
class ModelB: public Airplane{
public:
	virtual void fly(const Airport& destination)
	{defaultFly(destination);}
};
//對於C型飛機,自己提供自己的飛行行為版本即可。
class ModelC: public Airplane{
public:
	virtual void fly(const Airport& destination);
	
};
void ModelC::fly(const Airport& destination)
{
	//將C型飛機飛至指定目的地
}
第二種方式:藉助純虛擬函式自己的實現函式來完成相同的功能。以純虛擬函式的實現替代了獨立函式實現預設行為。

純虛擬函式fly被分割成兩部分:其函式宣告部分表現的是介面,派生類必須繼承該介面。定義部分(函式實現部分)表現出預設行為。預設行為是派生類可能使用的。

class Airplane{
public:
	virtual void fly(const Airport& destination) = 0;
};

void Airplane::fly(const Airport& destination)  //純虛擬函式實現
{
	//預設行為
}

class ModelA: public Airplane{
public:
	virtual void fly(const Airport& destination)
	{Airplane::fly(destination);}
};

class ModelB: public Airplane{
public:
	virtual void fly(const Airport& destination)
	{Airplane::fly(destination);}
};

class ModelC: public Airplane{
public:
	virtual void fly(const Airport& destination);
};

void ModelC::fly(const Airport& destination)
{
	//將C型飛機飛至指定目的地
}

例子3:成員函式宣告為非虛擬函式。目的是為了令派生類繼承函式的介面及一份強制性實現。

class Shape{
public:
	int objectID() const;
};
成員函式宣告為非虛擬函式,表示其並不打算在派生類中有不同的行為。也就是不論派生類多麼特異化,其行為都不可以改變。
非虛擬函式代表的意義是不變形凌駕特異性。它絕不應該在派生類中被重新定義。