http://blog.csdn.net/starlee/article/details/945728
我們知道,在物件導向語言中,公有繼承是IS-A的關係,也就是說子類是一種基類,就像說轎車是一種汽車一樣。但是,有時候邏輯上正確的公有繼承卻會違反替換原則。物件導向設計中的替換原則是:
子類必須能夠替換掉它們的基類。
也就是說,代碼中基類出現的地方都能用子類來替換,就跟汽車能用的地方都能用轎車一樣。但是,如果設計不合理,就會違反這個原則,給開發帶來隱患。
下面就以一個C++的例子來說明:
class CShape
{
public:
virtual ~CShape() {};
virtual double GetArea() = 0;
};
class CRectangle : public CShape
{
public:
virtual ~CRectangle() {};
double GetArea() { return m_dWidth * m_dHeight; };
void SetWidth(double dWidth) { m_dWidth = dWidth; };
void SetHeight(double dHeight) { m_dHeight = dHeight; };
double GetWidth() const { return m_dWidth; };
double GetHeight() const { return m_dHeight; };
private:
double m_dWidth;
double m_dHeight;
};
現在,我們需要一個正方形類CSquare。我們知道,正方形是一種寬和高相同的長方形,也就是說正方形和長方形符合IS-A的關係。那我們需要的類CSquare是否應該從類CRectangle來繼承呢?
我們現在先來看看類CSquare從類CRectangle繼承的情況:
class CSquare : public CRectangle
{
public:
virtual ~CSquare() {};
void SetWidth(double dWidth)
{
CRectangle::SetWidth(dWidth);
CRectangle::SetHeight(dWidth);
};
void SetHeight(double dHeight)
{
CRectangle::SetHeight(dHeight);
CRectangle::SetWidth(dHeight);
};
};
類CSquare中對方法SetWidth和方法SetHeight的重載解決了正方形的寬和高相等的問題。但是請考慮下面的代碼:
void FncTest(CRectangle& r)
{
r.SetWidth(13);
assert(r.GetArea() == 169);
}
void Test()
{
CSquare s;
s.SetWidth(5);
FncTest(s);
}
當我們向函數FncTest傳遞一個指向類CSquare對象的引用的時候,這個函數就會出現斷言錯誤,因為類CSquare對象的高度沒有改變。這就違反了替換原則,函數FncTest中的類型為基類CRectangle的參數不能用子類CSquare來替換。錯誤原因很簡單,類CRectangle中的方法SetWidth和方法SetHeight不是虛函數,因此不具備多態性。在這種情況下,我們就必須去修改類CRectangle。下面是修改後的代碼:
class CRectangle : public CShape
{
public:
virtual ~CRectangle() {};
double GetArea() { return m_dWidth * m_dHeight; };
virtual void SetWidth(double dWidth) { m_dWidth = dWidth; };
virtual void SetHeight(double dHeight) { m_dHeight = dHeight; };
double GetWidth() const { return m_dWidth; };
double GetHeight() const { return m_dHeight; };
private:
double m_dWidth;
double m_dHeight;
};
class CSquare : public CRectangle
{
public:
virtual ~CSquare() {};
virtual void SetWidth(double dWidth)
{
CRectangle::SetWidth(dWidth);
CRectangle::SetHeight(dWidth);
};
virtual void SetHeight(double dHeight)
{
CRectangle::SetHeight(dHeight);
CRectangle::SetWidth(dHeight);
};
};
這樣,上面提到的問題就解決了,修改也不是很複雜。然而,如果衍生類別的建立會導致我們修改基類,這就意味著設計是有缺陷的。我們先不管是否有設計缺陷,而是先接受上面的修改。現在類CRectangle和類CSquare都能工作,在代碼中也可以接受指向類CRectangle的指標或引用的函數傳遞類CSquare,而類CSquare也可以很好的保持正方形的特性。看起來,這樣的設計已經符合替換原則了。但是,等等,請看下面的函數:
void FncTest(CRectangle& r)
{
r.SetWidth(5);
r.SetHeight(6);
assert(r.GetArea() == 30);
}
對這個函數來說如果傳遞來的是類CRectangle的對象,則運行正確;如果傳遞來的是類CSque的對象,那就會出現斷言錯誤!仍舊是違反了替換原則!
上面的例子說明了,即使兩個類是IS-A的關係,也不一定要用公有繼承來實現。
其實,在物件導向設計(OOD)中,IS-A關係是就行為方式而言的。在本例中,雖然類CRectangle和類CSquare在邏輯上是IS-A關係,但是他們的行為是不同的,因此也就不能讓類CSquare從類CRectangle公有繼承,而是從類CShape公有繼承。
下面是正確的代碼:
class CShape
{
public:
virtual ~CShape() {};
virtual double GetArea() = 0;
};
class CRectangle : public CShape
{
public:
virtual ~CRectangle() {};
double GetArea() { return m_dWidth * m_dHeight; };
void SetWidth(double dWidth) { m_dWidth = dWidth; };
void SetHeight(double dHeight) { m_dHeight = dHeight; };
double GetWidth() const { return m_dWidth; };
double GetHeight() const { return m_dHeight; };
private:
double m_dWidth;
double m_dHeight;
};
class CSquare : public CShape
{
public:
virtual ~CSquare() {};
double GetArea() { return m_dWidth * m_dWidth; };
void SetWidth(double dWidth) { m_dWidth = dWidth; };
double GetWidth() const { return m_dWidth; };
private:
double m_dWidth;
};