MVC in MFC or WTL


關於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不適合小中型引用程序,多大才算中小呢?只有自己去開發體會了)

    image

    先來看下代碼結構:
    image
     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


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM