關於MVC
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傳給view24: }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