Chinaunix首页 | 论坛 | 博客
  • 博客访问: 627155
  • 博文数量: 133
  • 博客积分: 1566
  • 博客等级: 上尉
  • 技术积分: 1230
  • 用 户 组: 普通用户
  • 注册时间: 2010-12-01 09:31
文章分类

全部博文(133)

文章存档

2019年(1)

2018年(1)

2017年(8)

2016年(9)

2015年(17)

2014年(4)

2013年(31)

2012年(25)

2011年(36)

2010年(1)

我的朋友

分类: C/C++

2013-09-29 09:54:59

可以先看一个帖子:

关于MVC结构模式无须多言,如下图所示:


在MFC中实现经典的MVC结构,如下图所示:


注意:

1、Document和View之间的绑定:View绑定到Document。
2、Frame作为Controller时,由于可以接收到输入消息,比较适合充当控制器。但是,由于视图和文档绑定,所以在切换视图时需要将视图和文档分离开,然后再进行切换。
3、View作为Contrller时,由于该视图和文档是绑定的,所以,可以通过该视图将其他视图与文档关联起来,其他视图并不直接和文档绑定。因此,可以通过隐藏和显示视图来实现不同视图之间的切换。
4、MFC的Doc/View结构本质上就是MVC结构,是MVC结构的一种实现,但是对MVC结构进行了简化。

——————————————————————————————————————————————————————————————

MVC(Model-View-Controller)模式的基本思想是数据,显示和处理相分离。模型(Model)负责数据管理,视图(View)负责数据显示,控制器(Controller)负责业务逻辑和响应策略。

    从MVC的形成过程来看,最初只有模型和视图两个元素。模型封装了数据并提供操作接口,视图用来表现数据和接收用户请求。模型是独立的,而视图依赖于模型:从模型获取数据进行显示;向模型发送用户请求,并根据返回结果刷新自己。

    需要用多个视图表现同一模型时,情况发生了变化:一个视图修改数据以后,不但本身要刷新,其他所有视图也要刷新。如果由该视图通知其他视图,它就需要知道其他所有视图,由于每个视图都可能发出修改,每个视图都要知道其他所有视图,这种关联过于复杂,不但难以维护,而且不便于增加新的视图。如果让模型通知所有视图更新,可能会影响模型的独立性。用观察者(Observer)模式可以解决上述矛盾,从而实现:由模型通知视图,而模型不依赖于具体的视图,具体视图之间相互独立。

    视图是用户请求的接收者,但不宜作为请求的处理者。因为界面是易变的,如果业务代码和界面代码放在一起,频繁的界面修改可能会破坏比较稳定的业务代码。将业务逻辑分离出来,由一个控制器负责,就是为了避免这种干扰

    模型,视图和控制器的基本协作关系如下图

    模型在状态变化的时候,直接通知所有视图,视图向模型查询状态数据,然后刷新自身。当用户发出操作时,视图把消息发给控制器,控制器按照业务逻辑进行处理,需要查询或更新数据时,控制器会调用模型。下面是一个更详细的示意图

    同样的数据,可以有不同的显示和进行各种处理。显示仅仅是表现数据,而处理是根据用户请求改变数据的过程,不但包含业务逻辑,也要提供响应策略。响应策略由控制器负责,视图可以使用不同的控制器提供不同的响应方式,这是策略(Strategy)模式的应用。

    此外,MVC还允许视图嵌套,通过使用组合(Composite)模式,一致地处理组合视图和普通视图。

    用多个视图表现一个模型,在视图不变的情况下改变响应策略,允许视图嵌套,这是MVC的三个主要特性。在内部结构上,MVC的主要关系是由观察者模式,策略模式和组合模式给出的。由观察者模式确定的模型视图关系是其中最为重要的。

    MVC模式有许多变体。前述结构中,由模型通知视图刷新,称为主动MVC;如果由控制器更新模型以后通知视图,称为被动MVC结构。在许多应用中,没有明显的控制器角色,也没有视图嵌套。可见根据实际需要,构成MVC的三个模式上都可能出现变化。Web浏览器就是被动MVC结构的一个实例。 
   “浏览器是一个交互程序,从概念上讲,它是由一组客户、一组解释器与一个管理它们的控制器所组成。控制器形成了浏览器的中心部件,它解释鼠标点击与键盘输入,并且调用其他组件来执行用户指定的操作。例如,当用户键入一个URL或者点击一个超文本引用时,控制器调用一个客户从所需文档所在的远程服务器上取回该文档,并且调用解释器向用户显示该文档。每个浏览器必须包含一个HTML解释器来显示文档,其他解释器是可选的。HTML解释器的输入由符合HTML语法的文档所组成,输出由位于用户显示器上的格式版本文档所组成。解释器通过将HTML规则转换成适合用户显示硬件的命令来处理版面细节。HTML解释器一个最重要的功能是包含可选项。解释器必须存储关于显示器上位置之间关系的信息和HTML文档中被瞄定的项。当用户用鼠标选定了一个项,浏览器通过当前的光标位置和存储的位置信息来决定哪个项被用户选定。”(参考资料5)


MFC的文档/视图结构(Document/View architecture)是MVC模式的一种变体,下面讨论它是怎样实现的。

文档/视图结构没有体现业务逻辑和视图的分离,但是将响应策略和视图区分开来。它主要包含四种对象:

  1. 文档
  2. 视图
  3. 视图框架窗口
  4. 文档模板

这里的视图框架窗口定义了视图对用户输入的响应方式,而文档模板用来管理前三种对象的组合。文档,视图,视图框架窗口三者是对应的,从而构成一个三元组。一个应用程序可能需要多个这样的三元组,以实现文档的多视图,所以引入文档模板来表示该三元组。因为程序中可能使用多个文档模板,MFC用一个文档管理者对象来管理它们。
      在MFC中,应用程序和主框架窗口是用来封装底层机制的对象,文档,视图,视图框架窗口和文档模板是用来构架文档/视图结构的对象。应用程序通过文档管理者来使用文档/视图结构。

       如果要给文档增加一种视图,只需要增加一个文档模板;如果要改变一种视图的响应策略,只要改变对应文档模板中的视图框架窗口。

————————————————————————————————————————————————————————————————————

维基百科上MFC下MVC结构的解释:


[]


所推出的 Document/View架构是早期对于MVC模式的实现,將程式分成CView以及CDocument兩大類別,其中的Document对应MVC中的Model,View相当于MVC中的View+Controller,再加上CWinApp類別,合成三大項。但是基本上是一個失敗的MVC模式作品。

由於之下的Document/View定義過於模糊,未將Controller(MessageMap)部份取出,因此Controller可以置入View或Document,但不管置入哪一方面,都會與View或Document綁死,沒有彈性。

———————————————————————————————————————————————————————————

比较详细深入的讲解

MVC in MFC or WTL 

关于MVC


    MVC是一种分离用户界面和业务逻辑的开发架构。 
    ●  模型(Model):体现应用程序业务信息(数据)和业务数据的处理。所有有关数据库的操作只限制在该模型中。 
    ●  视图(View): 代表用户交互界面 
    ●  控制器(Contrlloer):控制器负责接收、截取用户请求(如键盘输入,鼠标点击),但不处理业务信息,它只把用户的信息传递给模型,告诉模型该做什么,由模型返回最终的处理结果。控制器再选择符合要求的视图返回给用户。

背景

    做Web或者Java的对MVC会比较熟悉,对于用MFC开发桌面应用程序的developer来说,已经习惯于拖一个按钮,然后双击,在CxxxDlg.cpp中添加事件响应。随着业务逻辑的复杂,这一个文件包含了所有的界面代码,逻辑处理,数据操作…。频繁的界面修改可能会破坏比较稳定的业务代码。将业务逻辑分离出来,由一个控制器负责,就可以避免这种干扰。

    去搜索了下MVC在桌面应用程序开发上的资料,找出两篇: 
    1. vckbase上的,。处理流程:Controller(CMVCSphereDlg)捕获后用户输入后通知Model(CSphere),Model再通知两个View(TextView & CGraphicView)更新显示。由模型通知视图刷新 
    
2. codeproject上的,。处理流程:View(frmCalcView)捕获用户事件后传递给Controller(CalcController), Controller调用Model(CalculatorModel)的运算方法得到计算结果,再回传给View更新显示。由控制器通知视图刷新

    看完这两个已经搞不定到底哪种才是真正的MVC,后来又查了资料说: 
    
MVC模式有许多变体。第一种,由模型通知视图刷新,称为主动MVC;如果由控制器更新模型以后通知视图,称为被动MVC结构。在许多应用中,没有明显的控制器角色,也没有视图嵌套。可见根据实际需要,构成MVC的三个模式上都可能出现变化。Web浏览器就是被动MVC结构的一个实例。

实践

    我将上面第二个用C#写的计算器的例子,改用WTL作为界面库,采用MVC架构设计。(据说MVC不适合小中型引用程序,多大才算中小呢?只有自己去开发体会了)

    image

    先来看下代码结构: 
    image 
     Model,View,Controller分开,Resource存在一些资源文件。

    1. 先来看下App类,怎么将三者组织起来的


点击(此处)折叠或打开

  1. 1: CMessageLoop theLoop;
  2.   2: _Module.AddMessageLoop(&theLoop);
  3.   3:
  4.   4: // View
  5.   5: CMainDlg dlgMain;
  6.   6: if(dlgMain.Create(NULL) == NULL)
  7.   7: {
  8.   8: ATLTRACE(_T("Main dialog creation failed!\n"));
  9.   9: return 0;
  10.  10: }
  11.  11: // Model
  12.  12: CalcModel* pModel = new CalcModel();
  13.  13: // Controller
  14.  14: CalcController* pController = new CalcController(pModel, &dlgMain);
  15.  15:
  16.  16: dlgMain.ShowWindow(nCmdShow);
  17.  17:
  18.  18: int nRet = theLoop.Run();
  19.  19:
  20.  20: _Module.RemoveMessageLoop();
  21.  21:
  22.  22: // 先析构哪个呢?
  23.  23: delete pController;
  24.  24: delete pModel;

  2. CalcController类构造函数需要传递Model和View的指针,接收从View传递来的用户事件,然后调用Model和View中中方法来完成用户请求。

点击(此处)折叠或打开

  1. 1: // 运算控制器
  2.   2: //
  3.   3: ///////////////////////////////////////////////////////////////////////////
  4.   4: #ifndef _CALCCONTROLLER_H_
  5.   5: #define _CALCCONTROLLER_H_
  6.   6:
  7.   7: #include "CalcView.h"
  8.   8: #include "CalcModel.h"
  9.   9:
  10.  10: class CalcController
  11.  11: {
  12.  12: protected:
  13.  13: CalcModel* m_pCalcModel;
  14.  14: CalcView* m_pCalcView;
  15.  15:
  16.  16: public:
  17.  17: CalcController(CalcModel* pModel, CalcView* pView)
  18.  18: : m_pCalcModel(pModel) // Model
  19.  19: , m_pCalcView(pView) // View
  20.  20: {
  21.  21: ATLASSERT(m_pCalcView);
  22.  22: ATLASSERT(m_pCalcModel);
  23.  23: m_pCalcView->AddController(this); // 将controller传给view
  24.  24: }
  25.  25:
  26.  26: // ......省略部分代码
  27.  27:
  28.  28: // 用户点击数值(0~9)
  29.  29: virtual void ClickValue(double dValue)
  30.  30: {
  31.  31: // ......省略
  32.  32:
  33.  33: // 生成新操作数
  34.  34: CString strValue;
  35.  35: strValue.Format(_T("%g"), dValue);
  36.  36: m_strOperateValue += strValue;
  37.  37:
  38.  38: m_clickType = click_value;
  39.  39: m_pCalcView->ShowOperateResult(m_strOperateValue);
  40.  40: }
  41.  41:
  42.  42: // 用户点击操作(+, -, *, ÷)
  43.  43: virtual void ClickOperate(OPERATE_TYPE op_type)
  44.  44: {
  45.  45: // ......省略
  46.  46:
  47.  47: // 计算出上一次运算符的结果
  48.  48: double dResult;
  49.  49: dResult = m_pCalcModel->Calc(m_operaType, _tstof(m_strOperateValue));
  50.  50: CString strResutl;
  51.  51: strResutl.Format(_T("%f"), dResult);
  52.  52: strResutl.TrimRight('0');
  53.  53: strResutl.TrimRight('.');
  54.  54:
  55.  55: // 更新view
  56.  56: m_pCalcView->ShowOperateExpression(m_strExpression);
  57.  57: m_pCalcView->ShowOperateResult(strResutl);
  58.  58:
  59.  59: // ......省略
  60.  60: }
  61.  61:
  62.  62: // ......省略
  63.  63: };
  64.  64:
  65.  65:
  66.  66: #endif // _CALCCONTROLLER_H_

    注意到,在Controller中,View和Model是没有直接交互的。通过在Controller中调用View的方法(ShowOperateResult,ShowOperateExpression)来更新View中的显示。那么View又是怎么传递用户事件给Controller的呢? 
    3. 通过m_pCalcView->AddController(this)  将Controller传给View。View就可以在接收到用户请求之后,就可以调用Controller中的事件处理函数(ClickValue,ClickOperate

点击(此处)折叠或打开

  1. 1: class CalcView
  2.   2: {
  3.   3: public:
  4.   4: CalcView()
  5.   5: : m_pCalcController(NULL)
  6.   6: {
  7.   7: }
  8.   8:
  9.   9: void AddController(CalcController* pController)
  10.  10: {
  11.  11: ATLASSERT(pController);
  12.  12: m_pCalcController = pController;
  13.  13: }
  14.  14:
  15.  15: // interface
  16.  16: // 显示运算表达式
  17.  17: virtual void ShowOperateExpression(CString strExpression) {}
  18.  18: // 显示运算结果
  19.  19: virtual void ShowOperateResult(CString strResutl) {}
  20.  20:
  21.  21: protected:
  22.  22: CalcController* m_pCalcController;
  23.  23: };

    CMainDlg从CalcView继承,重载接口实现计算结果的显示。MVC的一个目标就是把用户界面分离,如果要换一个界面,或者改用MFC编写界面了,只需要重载接口改变显示方式而已

点击(此处)折叠或打开

  1. 1: class CMainDlg : public CDialogImpl<CMainDlg>
  2.   2: , public CUpdateUI<CMainDlg>
  3.   3: , public CMessageFilter
  4.   4: , public CIdleHandler
  5.   5: , public CalcView
  6.   6: {
  7.   7: public:
  8.   8: // ......省略
  9.   9:
  10.  10: BEGIN_MSG_MAP(CMainDlg)
  11.  11: MESSAGE_HANDLER(WM_INITDIALOG, OnInitDialog)
  12.  12: COMMAND_ID_HANDLER(IDC_BUTTON_VAULE0, OnClickValue)
  13.  13: COMMAND_ID_HANDLER(IDC_BUTTON_VAULEP, OnClickDecimalPoint)
  14.  14: COMMAND_ID_HANDLER(IDC_BUTTON_ADD, OnClickOperate)
  15.  15: // ......省略
  16.  16: END_MSG_MAP()
  17.  17:
  18.  18: //////////////////////////////////////////////////////////////////////////
  19.  19: LRESULT OnClickValue(WORD, WORD , HWND hWndCtl , BOOL&)
  20.  20: {
  21.  21: CString strValue;
  22.  22: ::GetWindowText(hWndCtl, strValue.GetBuffer(1), 2);
  23.  23: strValue.ReleaseBuffer();
  24.  24:
  25.  25: m_pCalcController->ClickValue(_tstof(strValue));
  26.  26:
  27.  27: return 0;
  28.  28: }
  29.  29:
  30.  30: LRESULT OnClickDecimalPoint(WORD, WORD , HWND , BOOL&)
  31.  31: {
  32.  32: m_pCalcController->ClickDecimalPoint();
  33.  33: return 0;
  34.  34: }
  35.  35:
  36.  36: LRESULT OnClickOperate(WORD, WORD wID, HWND , BOOL&)
  37.  37: {
  38.  38: switch (wID)
  39.  39: {
  40.  40: case IDC_BUTTON_ADD:
  41.  41: m_pCalcController->ClickOperate(OP_ADD);
  42.  42: break;
  43.  43: case IDC_BUTTON_SUB:
  44.  44: m_pCalcController->ClickOperate(OP_SUB);
  45.  45: break;
  46.  46: case IDC_BUTTON_MULT:
  47.  47: m_pCalcController->ClickOperate(OP_MULT);
  48.  48: break;
  49.  49: case IDC_BUTTON_DIVE:
  50.  50: m_pCalcController->ClickOperate(OP_DIVE);
  51.  51: break;
  52.  52: }
  53.  53:
  54.  54: return 0;
  55.  55: }
  56.  56:
  57.  57: //////////////////////////////////////////////////////////////////////////
  58.  58:
  59.  59: // Override
  60.  60: void ShowOperateResult(CString strResutl)
  61.  61: {
  62.  62: GetDlgItem(IDC_STATIC_RESULT).SetWindowText(strResutl);
  63.  63: }
  64.  64:
  65.  65: void ShowOperateExpression(CString strExpression)
  66.  66: {
  67.  67: GetDlgItem(IDC_STATIC_EXPRESSION).SetWindowText(strExpression);
  68.  68: }
  69.  69: };

4. 最后看一下Model类,只负责数值计算,以及返回运算结果

点击(此处)折叠或打开

  1. 1: class CalcModel
  2.   2: {
  3.   3: public:
  4.   4: CalcModel() : m_dResult(0)
  5.   5: {
  6.   6: }
  7.   7:
  8.   8: // 执行计算,返回计算结果
  9.   9: double Calc(OPERATE_TYPE op_type, double dValue)
  10.  10: {
  11.  11: switch ( op_type )
  12.  12: {
  13.  13: case OP_NULL: // 第一个操作数默认执行和0相加
  14.  14: case OP_ADD: // 加法
  15.  15: Add(dValue);
  16.  16: break;
  17.  17: case OP_SUB: // 减法
  18.  18: Sub(dValue);
  19.  19: break;
  20.  20: case OP_MULT: // 乘法
  21.  21: Mult(dValue);
  22.  22: break;
  23.  23: case OP_DIVE: // 除法
  24.  24: Dive(dValue);
  25.  25: break;
  26.  26: }
  27.  27:
  28.  28: return m_dResult;
  29.  29: }
  30.  30:
  31.  31: protected:
  32.  32: //
  33.  33: void Add(double dValue)
  34.  34: {
  35.  35: m_dResult += dValue;
  36.  36: }
  37.  37:
  38.  38: //
  39.  39: void Sub(double dVaule)
  40.  40: {
  41.  41: m_dResult -= dVaule;
  42.  42: }
  43.  43:
  44.  44: //
  45.  45: void Mult(double dVaule)
  46.  46: {
  47.  47: m_dResult *= dVaule;
  48.  48: }
  49.  49:
  50.  50: //
  51.  51: void Dive(double dVaule)
  52.  52: {
  53.  53: m_dResult /= dVaule;
  54.  54: }
  55.  55:
  56.  56: protected:
  57.  57: double m_dResult; // 结果
  58.  58: };

完整代码:

(链接:http://www.cnblogs.com/yinxufeng/archive/2012/07/14/2591185.html

 

阅读(3033) | 评论(0) | 转发(0) |
给主人留下些什么吧!~~