緣起,看到一篇文章(懶得超鏈接),重新研究《C++沉思錄》的那一個課堂練習,綜合利用好幾種設計模式,並且很好地遵守結合的面向對象的原則,嗯,不管怎麼樣,還是表揚這位同學的面向對象與設計模式的功力,確實不容易。只是,在下從模式堆裡爬出來之後,已經對模式大倒胃口,看到這麼小的練習,居然要用上這麼多模式,一時技癢,有必要這樣嗎?對我來說,代碼中每引入一種模式,就表示要添加各種接口、虛函數,然後,免不了就要用到繼承,這些,我非常深痛惡疾,因為,這都意味著間接層越來越厚了,偏離了問題的本質。
先看看沉思錄對於這道題的描述,嗯,這個,還是請各位自己去看看書是怎麼寫的,作者提供了兩種方案,佩服作者和譯者的文筆,兩種方案解釋得相當細膩。下面,嘗試另一種解決方式。先秀一下代碼的運行效果。
對於這個課堂練習,其中,給圖片加邊框,最適合於用裝飾模式來解決。但是,兩幅圖片的橫接、豎接,怕是就不那麼容易了。但是,首先,要對問題重新描述一下,現實中,一張圖片,可以給予添加一層又一層的片框,也可以和其他的圖片組合在一塊,或橫,或豎,……,但是,圖片,始終只有一張,對它處理之後,它就一直是那個樣子了,不可能同時看到它的兩個樣子。如果,拿這張圖片去進行復制,那又自是另當別論,但那已經是另外一張新圖片了。
以下的設計用上了消息,如果有現成消息框架給予支持,實現起來就會很簡單。但是,即使沒有消息框架的支持,利用消息發送來解決這個問題,也是相當小兒科的事情。為了突出重點,忽略了各種異常處理,沒有優化,也不管什麼編程風格,純粹直奔主題。解決這個例子的最重要一點,就在於打印圖片時,要一行一行地從頂到底順次打印下來。因此,先定義這個圖片可以發送的消息,它必須處理兩條消息,圖片的高度(即行數),和圖片每一行的數據。即如下所示
enum {PM_HEIGHT, PM_ROW};
struct PictureMsg
{
typedef int (*ProcPictureMsg)(void* pThis, int nMessage, void* param1, void* param2);
public:
void* pThis;
ProcPictureMsg proc;
int GetHeight()
{
return (*proc)(pThis, PM_HEIGHT, NULL, NULL);
}
int GetRow(int nRow, string& sRow)
{
return (*proc)(pThis, PM_ROW, (void*)nRow, (void*)&sRow);
}
};
ostream& operator << (ostream& out, PictureMsg& msg)
{
int nHeight = msg.GetHeight();
string sRow;
for (int nRow = 0; nRow<nHeight; nRow++)
{
sRow.clear();
msg.GetRow(nRow, sRow);
out << sRow;
out << endl;
}
return out;
} www.2cto.com
其中的回調函數用以發送消息,本來回調函數搞成成員函數會更加好,但由於c++的成員函數是一個大坑,而我又不想引入function或者其他delegate等龐然大物。為了讓客戶更加方便地發送消息,特意編寫了兩個輔助函數。
但是,要發送消息給對象時,首先要求這個對象必須已經活著了,也即是要創建一個能接收消息的對象。由於沒有消息框架的支持,同時,也為了使用上的方便,我只好編寫一個具體的類繼承於圖片消息結構,並提供了初步的消息處理,嗯,用到了繼承,很難受。
好了,接下來就可以全心全意地對消息進行特別地處理了。要求,對圖片的各種裝飾操作,不能影響到圖片原有的功能,也不要求改變用戶的代碼。或者說,圖片本身並不知道自己已經被外界做了各種各樣的處理,它還是按照原來的樣子處理各種消息。下面,只是實現了兩種裝飾操作,加邊框與橫接,至於豎接,實在很容易實現的。此外,由於在實現裝飾操作時,需要用到圖片的寬度,因此,圖片消息又添加了一條新的消息,用以處理圖片的寬度。
由於是玩具代碼,在下也沒有做太大的優化,有幾行代碼顯得特別難看,特別是橫接的裝飾方法,強制指定了左邊的圖片為操作後的結果,還有各個裝飾類的析構函數,可以撤消對圖片的裝飾,以及添加新的消息,以返回圖片的拷貝,或返回當前正在進行的裝飾操作,或者裝飾類的動態創建等等。總之,可優化升級的方法多種多樣,而且都不會影響代碼的結構。以下,是一陀代碼,從第一版上稍加優化的,不讀也罷。
enum { PM_WIDTH, PM_HEIGHT, PM_ROW};
struct PictureMsg
{
typedef int (*ProcPictureMsg)(void* pThis, int nMessage, void* param1, void* param2);
public:
void* pThis;
ProcPictureMsg proc;
int GetWidth()
{
return (*proc)(pThis, PM_WIDTH, NULL, NULL);
}
int GetHeight()
{
return (*proc)(pThis, PM_HEIGHT, NULL, NULL);
}
int GetRow(int nRow, string& sRow)
{
return (*proc)(pThis, PM_ROW, (void*)nRow, (void*)&sRow);
}
};
ostream& operator << (ostream& out, PictureMsg& msg)
{
int nHeight = msg.GetHeight();
string sRow;
for (int nRow = 0; nRow<nHeight; nRow++)
{
sRow.clear();
msg.GetRow(nRow, sRow);
out << sRow;
out << endl;
}
return out;
}
class CPicture : public PictureMsg
{
public:
CPicture(const char* pDatas[], int nCount)
{
m_pDatas = pDatas;
m_nCount = nCount;
m_nWidth = 0;
for (int i=0; i<m_nCount; i++)
{
int nLen = strlen(m_pDatas[i]);
if (m_nWidth < nLen)
m_nWidth = nLen;
}
pThis = this;
proc = HandleMsg;
}
private:
const char** m_pDatas;
int m_nCount;
int m_nWidth;
static int HandleMsg(void* pThis, int nMessage, void* param1, void* param2);
};
int CPicture::HandleMsg(void* pThis, int nMessage, void* param1, void* param2)
{
CPicture* pSelf = (CPicture*)pThis;
switch (nMessage)
{
case PM_WIDTH:
return pSelf->m_nWidth;
case PM_HEIGHT:
return pSelf->m_nCount;
break;
case PM_ROW:
int nRow = (int)param1;
string& sRow = *(string*)param2;
if (nRow >= pSelf->m_nCount)
break;
int i=0;
for (; pSelf->m_pDatas[nRow][i] != 0; i++)
sRow.push_back(pSelf->m_pDatas[nRow][i]);
for (; i<pSelf->m_nWidth; i++)
sRow.push_back(' ');
}
return 0;
}
class CFrameDecorater
{
public:
CFrameDecorater(PictureMsg& imp)
{
m_PrevImp = imp;
imp.pThis = this;
imp.proc = HandleMsg;
}
private:
PictureMsg m_PrevImp;
static int HandleMsg(void* pThis, int nMessage, void* param1, void* param2);
};
int CFrameDecorater::HandleMsg(void* pThis, int nMessage, void* param1, void* param2)
{
CFrameDecorater* pSelf = (CFrameDecorater*)pThis;
PictureMsg& prevImp = pSelf->m_PrevImp;
switch (nMessage)
{
case PM_WIDTH:
return prevImp.GetWidth()+2;
case PM_HEIGHT:
return prevImp.GetHeight()+2;
case PM_ROW:
int nRow = (int)param1;
string& sRow = *(string*)param2;
bool bMyRow = nRow == 0 || nRow>prevImp.GetHeight();
if (nRow >= prevImp.GetWidth()+2)
break;
if (nRow == 0 || nRow>prevImp.GetHeight())
{
sRow.push_back('+');
for (int i=0; i<prevImp.GetWidth(); i++)
sRow.push_back('-');
sRow.push_back('+');
}
else
{
sRow.push_back('|');
prevImp.GetRow(nRow-1, sRow);
sRow.push_back('|');
}
}
return 0;
}
class CHorseDecorater
{
public:
CHorseDecorater(PictureMsg& impLeft, PictureMsg& impRight)
{
m_Left = impLeft;
m_Right = impRight;
impLeft.pThis = this;
impLeft.proc = HandleMsg;
}
private:
PictureMsg m_Left;
PictureMsg m_Right;
static int HandleMsg(void* pThis, int nMessage, void* param1, void* param2);
static void PrintRow(PictureMsg& pict, int nRow, string& sRow)
{
if (nRow < pict.GetHeight())
pict.GetRow(nRow, sRow);
else
{
for (int i=0; i<pict.GetWidth(); i++)
sRow.push_back(' ');
}
}
};
int CHorseDecorater::HandleMsg(void* pThis, int nMessage, void* param1, void* param2)
{
CHorseDecorater* pSelf = (CHorseDecorater*)pThis;
PictureMsg& pictLeft = pSelf->m_Left;
PictureMsg& pictRight = pSelf->m_Right;
switch (nMessage)
{
case PM_WIDTH:
return pictLeft.GetWidth()+pictRight.GetWidth();;
case PM_HEIGHT:
return max(pictLeft.GetHeight(), pictRight.GetHeight());
case PM_ROW:
int nRow = (int)param1;
string& sRow = *(string*)param2;
PrintRow(pictLeft, nRow, sRow);
PrintRow(pictRight, nRow, sRow);
}
return 0;
}
int main()
{
const char* init1[] = {"Paris", "in the", "Spring", "HaHa"};
CPicture pict1(init1, 3);
//cout << pict1;
CFrameDecorater framer1(pict1);
//cout << pict1;
CFrameDecorater framer2(pict1);
//cout << pict1;
CPicture pict2(init1, 4);
CHorseDecorater hors(pict1, pict2);
//cout << pict1;
CFrameDecorater framerHorse(pict1);
//cout << pict1;
CHorseDecorater hors2( pict2, pict1);
//cout << pict2;
CFrameDecorater framer3(pict2);
CHorseDecorater hors3( pict1, pict2);
cout << pict1;
return 0;
}
作者 huaxiazhihuo