比如現在我們有如下的幾個類:
class CGShape
{
public:
~CGShape();
virtual void Draw() = 0;
};
class CGRectangle : public CGShape
{
public:
void Draw();
};
class CGSquare : public CGShape
{
public:
void Draw();
};
class CGCircle : public CGShape
{
public:
void Draw();
};
如果現在有些代碼的使用者有一個特殊的需要,就是要在繪製出圖形的時候同時把圖形的中心點也繪製出來,那我們該怎麼辦呢?
很顯然,不能直接給各個類的Draw方法中加上繪製中心點的代碼,因為並不是所有的使用者都需要這個功能。
那麼我們可以給基類CGShape增添一個bool類型的成員變數m_bDrawCenter,並添加一個設定該變數值的公有方法SetDrawCenter,再在各個子類的Draw方法裡面根據變數m_bDrawCenter的值來判斷是否繪製中心點。需要繪製中心點的使用者在調用Draw方法之前先調用SetDrawCenter方法把m_bDrawCenter設定為true。
還有一個方法,就是分別以類CGRectangle、CGSquare和CGCircle為基類繼承,在各個子類中重載Draw方法,在該方法裡面添加繪製中心點的代碼。
上面兩種方法都可行,可是我們來仔細分析一下這兩種方法的缺陷:
方法一,每個類中的Draw方法中都要添加判斷變數m_bDrawCenter的值的代碼和繪製中心點的代碼,這些代碼在每個類中都是重複的,添加的工作也是非常枯燥無味。如果說類很多的話,那麼做這個工作的程式員會崩潰的。最重要的一點是,如果上面那些類都是包含在庫檔案中,沒有原始碼,那麼這個方法就根本行不通。
方法二,看起來似乎很符合物件導向設計(OOD),也不需要上面那些類的原始碼。但是,這樣會產生大量子類,並增加了類的階層。而且繼承下來的每個子類也都是修改Draw方法,而且添加的繪製中心點的代碼也都是重複的。
為了避免上面所說的各種缺陷,我們可以使用DECORATOR模式。看下面這個類:
class CGDrawShapeWithCenter : public CGShape
{
public:
CGDrawShapeWithCenter(CGShape *pShape);
void Draw();
private:
CGShape *m_pShape;
};
CGDrawShapeWithCenter::CGDrawShapeWithCenter(CGShape *pShape)
{
// 得到指向具體圖形的指標
m_pShape = pShape;
}
void CGDrawShapeWithCenter::Draw()
{
m_pShape->Draw();
// 在這裡添加繪製中心點的代碼
}
這個類使用起來非常簡單,下面這個函數就是繪製一個圓以及圓心:
void DrawCircleWithCenter()
{
CGCircle circle;
CGDrawShapeWithCenter shape(&circle);
shape.Draw();
}
在類CGDrawShapeWithCenter中,我們通過一個指向CGShape的指標獲得了要繪製的圖形的控制權,使用者在調用Draw方法時並沒有覺得有什麼不同。我們在即不會修改任何類,也不會產生大量子類,更不會有大量重複代碼的情況下,滿足了特殊使用者的需求。
當然我上面舉的這個例子非常簡單,但是足以說明DECORATOR模式的優點。需要注意的一點就是,類CGDrawShapeWithCenter必須實現所有類CGShape的介面,在不需要修改的介面實現中只要調用指向CGShape的指標m_pShape相應的方法就行了。
現在,有人也許會問:為什麼類CGDrawShapeWithCenter要從類CGShape繼承?從上面的代碼上來看,單獨寫一個類CGDrawShapeWithCenter也可以實現所有的功能。但是,請看下面這個函數:
void DrawShape(CGShape *pShape)
{
pShape->Draw();
}
這個函數是使用者寫來繪製圖形的。如果類CGDrawShapeWithCenter不是從類CGShape繼承,那麼要在繪製圖形的同時把圖形的中心也繪製出來,就必須修改這個函數:
void DrawShape(CGDrawShapeWithCenter *pShape)
{
pShape->Draw();
}
而如果類CGDrawShapeWithCenter是從類CGShape繼承來的,使用者就不用修改上面的函數。因為利用類的多態性,把一個類CGDrawShapeWithCenter對象的指標傳給函數就行了。比如下面這樣:
CGCircle circle;
CGDrawShapeWithCenter shape(&circle);
DrawShape(&shape);
也就是說,所有用CGShape的地方都能用CGDrawShapeWithCenter,從而發揮C++類的多態性的強大功能。在這種情況下,使用者代碼的修改量也可以降到最小。而這樣做最大的好處是decorator類CGDrawShapeWithCenter還能被其他decorator類來修飾。其實,DECORATOR模式最精妙的地方就是decorator類是從要修飾的類的基類繼承,而且有一個指向被修飾類的指標的成員。