設計模式是代碼重構的最終目標,在程序設計中有效的運用這項技術,可以大大提高代碼的可讀性和可維護性。使整個程序設計結構趨向精緻完美。下面我就從常見矢量繪圖的例子來闡述一些常見模式的運用。
矢量繪圖系統中,用戶需要用鼠標在視圖中繪製多種圖形(點,折線,多邊形,圓,曲線等),並對其進行編輯。
我們來看看鼠標繪製圖形的操作,這裏主要響應3個消息
OnLButtonDown()鼠標左鍵單擊消息,開始繪製圖形和圖形特徵點的定位.
OnMouseMove()鼠標移動消息,處理當前圖形特徵點的位置移動以及圖形隨之相應的動態繪製.
OnLButtonDblClk()鼠標左鍵雙擊消息,結束操作,完成圖形繪製。
我看過一些初學者寫的繪圖例子,在沒有學習設計模式情況下,大多數人的代碼可能就像以下這樣:
void CDrawView:: OnLButtonDown(UINT nFlags, CPoint point)
{
If (繪製點)
{
。。。
}
Else if(繪製折線)
{
。。。
}
Else if(繪製多邊形)
{
。。。
}
。。。
}
OnMouseMove(),OnLButtonDblClk()也與之類似。
這種代碼充斥大量的代碼分支,無論是閱讀還是維護都極其困難,我曾經看過長達1000多行這樣的函數,第一感覺就是暈眩。
我們來看看設計模式中對於狀態模式的描述.
在下面的情況下可以使用狀態模式:
1) 一個對象的行爲取決於它的狀態,並且它必須在運行時改變它的行爲.
2) 一個操作中含有龐大的多分支條件語句,且這些分支依賴於對象的狀態,這個狀態通常用一個或者多個枚舉常量來表示,通常有多個操作包含這一相同的條件結構.state將每一個分支條件放入一個獨立的類中,這使得你可以根據對象自身的情況將對象的狀態作爲一個對象,這個對象可以不依賴於其他對象而獨立變化.
這剛好適用於我們現在要處理的問題,我們可以設計一個類專門處理鼠標消息的類CMouseTool,它提供了與以上相對應的幾個接口:
OnLButtonDown(), OnMouseMove(),OnLButtonDblClk().
我們一個在CDrawView聲明一個指向當前繪製工具對象的指針變量
CMouseTool* m_pcCurTool
現在我們的程序可以簡單的這麼寫了:
void CDrawView:: OnLButtonDown(UINT nFlags, CPoint point)
{
if (m_pcCurTool)
m_pcCurTool-> OnLButtonDown (nFlags, point);
}
void CDrawView::OnMouseMove(UINT nFlags, CPoint point)
{
if (m_pcCurTool)
m_pcCurTool->OnMouseMove(nFlags, point);
}
void CDrawView:: OnLButtonDblClk (UINT nFlags, CPoint point)
{
if (m_pcCurTool)
m_pcCurTool-> OnLButtonDblClk (nFlags, point);
}
接下來就是鼠標工具類的具體設計了.
我們逐個分析一下這些鼠標操作的具體行爲.
在OnLButtonDown鼠標左鍵單擊消息中,我們要記錄:鼠標點擊的起始點, 鼠標當前點擊位置, 鼠標上一位置,鼠標當前位置,記錄鼠標此次操作點擊次數.並在繪製不同圖形的情況下去進行一些操作.我們可以看到除了” 在繪製不同圖形的情況去進行一些操作”之外,其餘都是所有圖形繪製工具子類共有的部分,因此OnLButtonDown函數在一般情況下,可以設計爲以下的方式.
void CMouseTool::OnLButtonDown(UINT nFlags, CPoint pt)
{
if (0L == m_lClickTimes)
m_cStartPoint = pt; // 記錄初始點
SetCurPoint(pt); // 設置當前點
m_cDownPoint = m_cLastPoint = m_cCurPoint;
m_lClickTimes++;
Perform_LButtonDown(nFlags);
}
其中Perform_LbuttonDown是私有的純虛函數,如果具現的鼠標工具子類的左鍵單擊消息遵從以上的行爲的話,只需要那麼重載Perform_LbuttonDown實現.比如多邊形繪製工具CPolygonTool就可以如此實現:
void CPolygonTool:: Perform_LButtonDown(UINT nFlags)
{
m_pcNewPolygon->AddPoint(m_cCurPoint);
}
這時候CMouseTool::OnLButtonDown()又表現爲另外一種經典的設計模式 - 模板模式.
我們看看模板模式的描述:
定義一個操作中的算法的骨架,而將一些步驟延續到子類中,Template模式使得子類可以不改變一個算法的結構即可重定義該算法某些特定步驟.
根據這個設計思想以及消息響應的具體情況,其它兩個消息響應函數也是類似:
void CMouseTool::OnMouseMove(UINT nFlags, CPoint pt)
{
// 尚未點擊觸發鼠標操作,不予處理
if (0L == m_lClickTimes)
return;
// 記錄鼠標軌跡
m_cLastPoint = m_cCurPoint;
SetCurPoint(pt);
CRect rc;
// 執行響應鼠標移動的操作
Perform_MouseMove(nFlags, rc);
// 繪製視圖
RefreshView(rc, &dc);
}
Void CMouseTool::OnLButtonDblClk(UINT nFlags,CPoint pt, CdrawView* pcView)
{
// 執行響應鼠標雙擊的操作
Perform_LButtonDblClk();
pcView->Invalidate();
m_lClickTimes = 0L;
}
其中的Perform_MouseMove(nFlags, rc), RefreshView (rc, &dc);Perform_LButtonDblClk()都是私有的純虛函數, 具現的鼠標工具子類的鼠標左鍵單擊消息遵從以上的行爲模式的話重載即可.
從上分析可以看到,對於鼠標操作的處理,整體上可以看作是狀態模式和模板模式的雜糅體,在實際工作中爲了解決某個問題,我們的使用的方式往往不是某一單一的模式,而是根據具體情況找出其中的規律,發揮自己的創造力靈活運用,這樣才能達到最好的效果.
另附上各類的基本接口代碼:
class CMouseTool
{
protected:
CMouseTool(const long lToolType, const UINT nCursorID);
virtual ~CMouseTool(){}
public:
/* ------------------------ 消息響應函數 ------------------------------- */
virtual void OnMouseMove(UINT nFlags,CPoint pt);
virtual void OnLButtonDown(UINT nFlags, CPoint pt);
virtual void OnLButtonDblClk(UINT nFlags, CPoint pt);
private:
/* ------------------------ 消息執行函數 ------------------------------- */
// 移動鼠標時響應的操作
virtual BOOL Perform_MouseMove(UINT nFlags, CRect& rc);
// 單擊鼠標左鍵時的操作
virtual void Perform_LButtonDown(UINT nFlags);
// 雙擊鼠標左鍵時響應的操作
virtual BOOL Perform_LButtonDblClk();
}