引言
设计模式(Design Pattern)是面向对象软件设计中已被证实了的设计经验的总结。利用设计模式可以更加简单方便的复用成功的设计和体系结构,帮助设计者更快更好的完成 设计,并可以大大提高系统的可扩展性、可移植性,优化系统的设计结构。一般来说,一个模式有四个基本要素组成:模式名称,问题,解决方案和后果。
MFC(Microsoft Foundation Class)是微软推出的一套开发Windows平台软件的规模宏大的类库,是一套应用框架。之所以说MFC是一套框架,最重要的特征它所提供的 View/Document结构能够将数据管理与显示分离。View/Document是MFC的基石。
分析MFC框架中所使用的设计模式即有利于通过MFC的代码实例来理解设计模式,也有利于通过设计模式来理解MFC的内部机理,更好地使用MFC。文章详 细分析了MFC的View/Document结构中所用到的设计模式,并以MFC 类库中提供的源码为例,阐明了各种模式在MFC中的实现原理,最后给出结论。
1、模板方法(Template Method)
模板方法是一种代码复用的基本技术。模板方法模式中,基类用一些抽象的操作定义了一个算法的骨架,子类重定义算法中的特定部分,以完成特定于子类的各种操作。模板方法模式的类结构如图1所示:
图1:模板方法类结构图
作为一种基本的代码复用技术,模板方法在MFC中得到大量的应用。如在MFC的源程序VIEWCORE.CPP中,类 CView(AbstractClass)对Windows消息WM_PAINT的相应函数OnPaint(TemplateMethod)。
首先,通过宏语句ON_WM_PAINT将WM_PAINT消息的处理映射到函数OnPaint。VIEWCORE.CPP中的OnPaint定义了对WM_PAINT的处理骨架,如下所示:
void CView::OnPaint()
{
// standard paint routine
CPaintDC dc(this);
OnPrepareDC(&dc);
OnDraw(&dc);
}
OnDraw被定义为纯虚函数,因此在生成代码框架时,AppWizard为CView的子类自动生成了OnDraw函数。用户只需在CView的 派生类中的OnDraw函数中编写代码就可实现实现数据的正确显示,而不必关心OnDraw如何被调用。OnprepareDC函数可用于在View中显 示数据前修改设备上下文,或者打印时对打印机进行控制等。MFC对OnPrepareDC提供了缺省实现,必要时,可以在派生类中重载该函数,实现用户特 定的功能。
2、职责链(Chain of Responsibility)
多个对象组成一个对象链,客户请求沿着对象链进行传播,直到有一个对象处理它。在职责链中,发出请求的对象不明确哪个对象是请求的接受者,也不明确对象链 的大小,链中的每一个对象都有可能对之进行处理。职责链中的对象大多按照从特殊到一般的顺序排列。类结构如图2所示:
图2:职责链类结构图
典型的对象链如图3所示:
图3:职责链中的对象链
Windows程序是消息驱动程序,MFC使用职责链设计模式,将Windows的消息映射封装到了各个需要使用相关消息的类中。
在MFC中,所有派生自CCmdTarget的类都能够响应命令消息(WM_COMMAND),所有派生自CWnd的类都能够响应标准Windows消息 (除WM_COMMAND之外的消息)。因CWnd派生于CCmdTarget类,故派生自CWnd的类也可响应命令消息。
MFC使用消息映射表来将个消息与相应的处理函数对应起来。MFC消息映射表是通过一组宏来实现的。实现消息映射表的类必须在其类定义(.h文件)中调用以下宏声明消息映射表:
DECLARE_MESSAGE_MAP()
并在其实现文件中调用一组宏来实现消息映射表。比如,若要在视图类(假设工程名字为Test,则视图类名为CTestView)中将WM_CREATE映射到函数OnCreate,则应在视图类的实现文件中加入如下宏:
BEGIN_MESSAGE_MAP(CTestView, CView)
ON_WM_CREATE()
END_MESSAGE_MAP()
对于非WM_COMMAND消息,消息沿着类的继 承方向逆向传递,直到找到相应的处理函数为止。
对于WM_COMMAND,因与窗口无关的类也要能够处理,故有不同的传递路径。以单文档应用为例,当用户点击了按钮或菜单 时,WM_COMMAND消息将会传送到CFrameWnd的子类CMainFrame的窗口过程函数(因这两个类都没改写WindowProc,实际上 为CWnd类的WindowProc),然后被转发到CFrameWnd类的OnCmdMsg函数:
//WinFrm.cpp
BOOL CFrameWnd::OnCmdMsg(UINT nID, int nCode, void* pExtra,
AFX_CMDHANDLERINFO* pHandlerInfo)
{
CPushRoutingFrame push(this);
// pump through current view FIRST
CView* pView = GetActiveView();
if (pView != NULL && pView->OnCmdMsg(nID, nCode, pExtra, pHandlerInfo))
return TRUE;
// then pump through frame
if (CWnd::OnCmdMsg(nID, nCode, pExtra, pHandlerInfo))
return TRUE;
// last but not least, pump through app
CWinApp* pApp = AfxGetApp();
if (pApp != NULL && pApp->OnCmdMsg(nID, nCode, pExtra, pHandlerInfo))
return TRUE;
return FALSE;
}
有以上程序可知,消息将首先送到视图进行处理。若不能处理,则转回框架窗口,若还不能处理,则送到CWinApp对象处理。消息到达视图类后,视图类将首先检查自己的消息映射表,若没有相应的处理函数,则转入文档类进行处理。消息传送的对象链如图4所示:
图4:WM_COMMAND消息传送链
3、观察者(Observer)
观察者模式又称为发布-订阅模式,多个观察者(Observer)与一个目标对象(Subject)存在依赖关系,当目标对象改变时,所有观察者都得到通知并被自动更新。
观察者模式中,目标和观察者分别封装在独立的对象中,目标可以不必清楚具体有多少个观察者,或具体的观察者对象是谁,只需知道观察者的抽象接口,从而两者可以独立地改变和复用。观察者模式的类结构如图5所示:
图5:观察者模式类结构图
当目标(Subject)的状态发生改变时,目标对象调用Notify(),通知与其关联的所有观察者。观察者得到通知后,调用目标对象的方法GetState()对自身状态进行更新。同时,观察者也可以主动改变目标的状态。
MFC的View/Document结构的实现中采用了观察者模式。Document为模式中的目标,管理应用程序中的数据,View为模式中的观察者, 以给定的方式显示所关联的Document中的数据。CDocument类中定义了一个指针列表,用于保存对应的CView对象,并定义了一个函数用于对 链表中的所有CView的对象进行更新。
//afxwin.h
class CDocument : public CcmdTarget
{
public:
void UpdateAllViews(CView* pSender, LPARAM lHint = 0L,
CObject* pHint = NULL);
……
protected:
CPtrList m_viewList; // list of views
……
}
//DocCore.cpp
void CDocument::UpdateAllViews(CView* pSender, LPARAM lHint, CObject* pHint)
{
ASSERT(pSender == NULL || !m_viewList.IsEmpty());
// must have views if sent by one of them
POSITION pos = GetFirstViewPosition();
while (pos != NULL)
{
CView* pView = GetNextView(pos);
ASSERT_VALID(pView);
if (pView != pSender)
pView->OnUpdate(pSender, lHint, pHint);
}
}
在执行打开文件或新建功能后,UpdateAllViews将会被自动调用,然后依次调用各个CView对象的OnUpdate,进行显示更新。
4、桥接(Bridge)
桥接模式用于将抽象部分与实现部分相分离,使它们都可以独立地变化。
使用桥接模式,抽象类的实现可以在运行时刻进行配置,一个对象甚至可以在运行时刻改变它的实现。接口与实现分离也有利于分层,实现部分处理底层的实现细节,接口部分提供高层接口,并可单独进行修改,扩充。桥接模式的类结构如图6所示:
图6:桥接模式类结构图
图中,右边的实现类Implementor实现较为低级的功能,可以只实现一个最小集,而由中间的接口类实现较为高级的功能。
MFC中对对象的存取称为串行化(Serialization)。串行化设计中采用了桥接设计模式。MFC使用Carchive类和CFile类(或 CFile的派生类)配合实现对象的串行化。CArchive实现了串行化时所使用的接口(比如重载操作符>>和<<),而不关 心数据保存在何处或何种介质上。CFile或其派生类则具体实现了从各种介质中(比如内存,磁盘,网络等)读取数据的机制。
CArchive在初始化时,通过构造函数的参数与一个CFile对象绑定起来,如下所示:
CArchive( CFile* pFile, UINT nMode, int nBufSize = 4096, void* lpBuf = NULL );
MFC框架中已经自动实现了文档的存取(使用模板方法),用户只需在其文档类中实现自己的Serialize函数即可。
CArchive类和CFile类可以互不影响地单独进行修改,比如,CArchive类可以派生出新的类以定义新的数据类型来重 载>>和<<操作符,实现新的数据类型的存取,或定义提供新的功能的函数,而CFile则可以派生出新的文件类,实现不同文件的 读取和写入,比如数据库的存取。
5、单件(Singleton)
单件模式保证一个类仅有一个实例,并且提供一个访问它的全局访问点。当一个类只允许有一个实例时可使用单件模式。
每一个MFC应用实例都派生于类CWinApp,显然,每个应用程序都只应该有一个派生于CwinApp的实例。CwinApp在设计上保证了一个 应用程序不能生成多个实例,并且提供了一系列的函数用于对该唯一对象的一些属性的访问,包括 AfxGetApp,AfxGetInstanceHandle,AfxGetResourceHandle,AfxGetAppName等。
MFC是通过ASSERT来防止多次构造CWinApp对象的。在第二次构造CwinApp对象时,ASSERT内的表达式为假,将会弹出错误提 示。然而,宏语句ASSERT只在debug版本中有效,但采用release版本编译时,会发现应用程序还是可能正确的运行,该实现并不完美。单件模式 通常可以通过类的静态变量和静态函数来实现。
6、中介(Mediator)
用一个中介对象来封装一系列的对象交互。中介模式将一系列对象间的多对多的通信转化为中介对象与各个对象的一对多的通信,从而使其耦合松散,而且可以独立的改变它们之间的交互。中介模式的类结构如图7所示:
图7:中介模式类结构图
MFC的对话框机制使用了中介模式。对话框类作为中介类(Mediator),对话框上的其它控件作为同事类(Colleague),如编辑框,按 钮,下拉链表框等。当对话框上的两个或多个控件需要通信时(如编辑框输入有效数据后使按钮使能),发送消息的控件首先将消息发送到它的父窗口(对话框), 然后父窗口将收到的消息转发到各目标控件。
结束语
MFC作为一套应用框架,使用了很多的设计模式。这些设计模式的使用,大大提高了类库的可复用性,可扩展性。比如,使用模板方法,开发人员只需对其感兴趣 的方法进行重写即可,大大减少了开发人员的工作,并且增强了开发人员的灵活性。用职责链模式,将Windows复杂的消息处理机制分布到各个需要处理消息 的类中,程序员只需使用几句简单的宏语句,即可实现Windows复杂的消息映射,大大简化了系统开发。
但MFC中独特的RTTI(Runtime Type Indentification)机制和动态创建功能减少了使用设计模式,特别是创建型模式的必要性。实际上,MFC的对象的串行化机制和动态创建机制也 可以认为是创建型模式中的抽象工厂模式和原型模式的变种,不过,MFC不使用继承机制实现,而是通过一些复杂的宏语句实现。
除了View/Document框架中使用的模式外,在别的类中还包含了许多其他的模式,如OLE中大量使用了工厂模式,集合类模板库中大量使用了迭代器模式等。
但由于MFC的设计时间比较早和其复杂性,很多适合使用设计模式的地方却没有使用,或者不太明显,有的模式的实现也不是很合理。MFC中宏语句的使用,大大减少了对象继承的系统开销,但也减低了程序的可读性,灵活性。
本文来自CSDN博客,转载请标明出处:http://blog.csdn.net/liulianxi/archive/2008/10/10/3053274.aspx