MVC是一种分离用户界面和业务逻辑的开发架构。
● 模型(Model):体现应用程序业务信息(数据)和业务数据的处理。所有有关数据库的操作只限制在该模型中。
● 视图(View): 代表用户交互界面
● 控制器(Contrlloer):控制器负责接收、截取用户请求(如键盘输入,鼠标点击),但不处理业务信息,它只把用户的信息传递给模型,告诉模型该做什么,由模型返回最终的处理结果。控制器再选择符合要求的视图返回给用户。
做Web或者Java的对MVC会比较熟悉,对于用MFC开发桌面应用程序的developer来说,已经习惯于拖一个按钮,然后双击,在CxxxDlg.cpp中添加事件响应。随着业务逻辑的复杂,这一个文件包含了所有的界面代码,逻辑处理,数据操作…。频繁的界面修改可能会破坏比较稳定的业务代码。将业务逻辑分离出来,由一个控制器负责,就可以避免这种干扰。
去搜索了下MVC在桌面应用程序开发上的资料,找出两篇:
1. vckbase上的,采用MFC编制MVC模式之球体演示程序。处理流程:Controller(CMVCSphereDlg)捕获后用户输入后通知Model(CSphere),Model再通知两个View(TextView & CGraphicView)更新显示。由模型通知视图刷新
2. codeproject上的,Simple Example of MVC (Model View Controller) Design Pattern for Abstraction。处理流程:View(frmCalcView)捕获用户事件后传递给Controller(CalcController), Controller调用Model(CalculatorModel)的运算方法得到计算结果,再回传给View更新显示。由控制器通知视图刷新
看完这两个已经搞不定到底哪种才是真正的MVC,后来又查了资料说:
MVC模式有许多变体。第一种,由模型通知视图刷新,称为主动MVC;如果由控制器更新模型以后通知视图,称为被动MVC结构。在许多应用中,没有明显的控制器角色,也没有视图嵌套。可见根据实际需要,构成MVC的三个模式上都可能出现变化。Web浏览器就是被动MVC结构的一个实例。
我将上面第二个用C#写的计算器的例子,改用WTL作为界面库,采用MVC架构设计。(据说MVC不适合小中型引用程序,多大才算中小呢?只有自己去开发体会了)
先来看下代码结构:
Model,View,Controller分开,Resource存在一些资源文件。
1. 先来看下App类,怎么将三者组织起来的
1: CMessageLoop theLoop;
2: _Module.AddMessageLoop(&theLoop);
3:
4: // View
5: CMainDlg dlgMain;
6: if(dlgMain.Create(NULL) == NULL)
7: {
8: ATLTRACE(_T("Main dialog creation failed!\n"));
9: return 0;
10: }
11: // Model
12: CalcModel* pModel = new CalcModel();
13: // Controller
14: CalcController* pController = new CalcController(pModel, &dlgMain);
15:
16: dlgMain.ShowWindow(nCmdShow);
17:
18: int nRet = theLoop.Run();
19:
20: _Module.RemoveMessageLoop();
21:
22: // 先析构哪个呢?
23: delete pController;
24: delete pModel;
2. CalcController类构造函数需要传递Model和View的指针,接收从View传递来的用户事件,然后调用Model和View中中方法来完成用户请求。
1: // 运算控制器
2: //
3: ///////////////////////////////////////////////////////////////////////////
4: #ifndef _CALCCONTROLLER_H_
5: #define _CALCCONTROLLER_H_
6:
7: #include "CalcView.h"
8: #include "CalcModel.h"
9:
10: class CalcController
11: {
12: protected:
13: CalcModel* m_pCalcModel;
14: CalcView* m_pCalcView;
15:
16: public:
17: CalcController(CalcModel* pModel, CalcView* pView)
18: : m_pCalcModel(pModel) // Model
19: , m_pCalcView(pView) // View
20: {
21: ATLASSERT(m_pCalcView);
22: ATLASSERT(m_pCalcModel);
23: m_pCalcView->AddController(this); // 将controller传给view
24: }
25:
26: // ......省略部分代码
27:
28: // 用户点击数值(0~9)
29: virtual void ClickValue(double dValue)
30: {
31: // ......省略
32:
33: // 生成新操作数
34: CString strValue;
35: strValue.Format(_T("%g"), dValue);
36: m_strOperateValue += strValue;
37:
38: m_clickType = click_value;
39: m_pCalcView->ShowOperateResult(m_strOperateValue);
40: }
41:
42: // 用户点击操作(+, -, *, ÷)
43: virtual void ClickOperate(OPERATE_TYPE op_type)
44: {
45: // ......省略
46:
47: // 计算出上一次运算符的结果
48: double dResult;
49: dResult = m_pCalcModel->Calc(m_operaType, _tstof(m_strOperateValue));
50: CString strResutl;
51: strResutl.Format(_T("%f"), dResult);
52: strResutl.TrimRight('0');
53: strResutl.TrimRight('.');
54:
55: // 更新view
56: m_pCalcView->ShowOperateExpression(m_strExpression);
57: m_pCalcView->ShowOperateResult(strResutl);
58:
59: // ......省略
60: }
61:
62: // ......省略
63: };
64:
65:
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: class CalcView
2: {
3: public:
4: CalcView()
5: : m_pCalcController(NULL)
6: {
7: }
8:
9: void AddController(CalcController* pController)
10: {
11: ATLASSERT(pController);
12: m_pCalcController = pController;
13: }
14:
15: // interface
16: // 显示运算表达式
17: virtual void ShowOperateExpression(CString strExpression) {}
18: // 显示运算结果
19: virtual void ShowOperateResult(CString strResutl) {}
20:
21: protected:
22: CalcController* m_pCalcController;
23: };
CMainDlg从CalcView继承,重载接口实现计算结果的显示。MVC的一个目标就是把用户界面分离,如果要换一个界面,或者改用MFC编写界面了,只需要重载接口改变显示方式而已。
1: class CMainDlg : public CDialogImpl<CMainDlg>
2: , public CUpdateUI<CMainDlg>
3: , public CMessageFilter
4: , public CIdleHandler
5: , public CalcView
6: {
7: public:
8: // ......省略
9:
10: BEGIN_MSG_MAP(CMainDlg)
11: MESSAGE_HANDLER(WM_INITDIALOG, OnInitDialog)
12: COMMAND_ID_HANDLER(IDC_BUTTON_VAULE0, OnClickValue)
13: COMMAND_ID_HANDLER(IDC_BUTTON_VAULEP, OnClickDecimalPoint)
14: COMMAND_ID_HANDLER(IDC_BUTTON_ADD, OnClickOperate)
15: // ......省略
16: END_MSG_MAP()
17:
18: //////////////////////////////////////////////////////////////////////////
19: LRESULT OnClickValue(WORD, WORD , HWND hWndCtl , BOOL&)
20: {
21: CString strValue;
22: ::GetWindowText(hWndCtl, strValue.GetBuffer(1), 2);
23: strValue.ReleaseBuffer();
24:
25: m_pCalcController->ClickValue(_tstof(strValue));
26:
27: return 0;
28: }
29:
30: LRESULT OnClickDecimalPoint(WORD, WORD , HWND , BOOL&)
31: {
32: m_pCalcController->ClickDecimalPoint();
33: return 0;
34: }
35:
36: LRESULT OnClickOperate(WORD, WORD wID, HWND , BOOL&)
37: {
38: switch (wID)
39: {
40: case IDC_BUTTON_ADD:
41: m_pCalcController->ClickOperate(OP_ADD);
42: break;
43: case IDC_BUTTON_SUB:
44: m_pCalcController->ClickOperate(OP_SUB);
45: break;
46: case IDC_BUTTON_MULT:
47: m_pCalcController->ClickOperate(OP_MULT);
48: break;
49: case IDC_BUTTON_DIVE:
50: m_pCalcController->ClickOperate(OP_DIVE);
51: break;
52: }
53:
54: return 0;
55: }
56:
57: //////////////////////////////////////////////////////////////////////////
58:
59: // Override
60: void ShowOperateResult(CString strResutl)
61: {
62: GetDlgItem(IDC_STATIC_RESULT).SetWindowText(strResutl);
63: }
64:
65: void ShowOperateExpression(CString strExpression)
66: {
67: GetDlgItem(IDC_STATIC_EXPRESSION).SetWindowText(strExpression);
68: }
69: };
4. 最后看一下Model类,只负责数值计算,以及返回运算结果
1: class CalcModel
2: {
3: public:
4: CalcModel() : m_dResult(0)
5: {
6: }
7:
8: // 执行计算,返回计算结果
9: double Calc(OPERATE_TYPE op_type, double dValue)
10: {
11: switch ( op_type )
12: {
13: case OP_NULL: // 第一个操作数默认执行和0相加
14: case OP_ADD: // 加法
15: Add(dValue);
16: break;
17: case OP_SUB: // 减法
18: Sub(dValue);
19: break;
20: case OP_MULT: // 乘法
21: Mult(dValue);
22: break;
23: case OP_DIVE: // 除法
24: Dive(dValue);
25: break;
26: }
27:
28: return m_dResult;
29: }
30:
31: protected:
32: // 加
33: void Add(double dValue)
34: {
35: m_dResult += dValue;
36: }
37:
38: // 减
39: void Sub(double dVaule)
40: {
41: m_dResult -= dVaule;
42: }
43:
44: // 乘
45: void Mult(double dVaule)
46: {
47: m_dResult *= dVaule;
48: }
49:
50: // 除
51: void Dive(double dVaule)
52: {
53: m_dResult /= dVaule;
54: }
55:
56: protected:
57: double m_dResult; // 结果
58: };
完整代码:MVC in MFC or WTL