说明
参考资料
http://www.newty.de/fpt/zip/e_fpt.pdf
示例
这里是C++的一个例子。
要点
类说明:
- CMathLib:执行某些算法(这里以加法运算为例),被其他类或函数调用;也可以理解为底层支持库。在示例中,要将这个类中的一个静态成员函数作为指针,传给其他的类(CCaller)
- CCaller:相当于对CMathLib的一层wrapper,用户(User)把CMathLib的算法函数作为函数指针,复制给CCaller。CCaller在执行具体的事务的时候,调用这个函数指针(相当于回调函数)真正去do something。
- main():相当于User。
User视角
#include "MathLib.h" #include "Caller.h" int _tmain(int argc,_TCHAR* argv[]) { CCaller caller(&CMathLib::Add); caller.DoSomething(); return 0; }
Caller.h,.cpp
头文件:
#pragma once typedef int (*AddFunc)(int a,int b); class CCaller { public: explicit CCaller(AddFunc f); ~CCaller(void); void DoSomething(); private: AddFunc m_addFunc; };
实现文件:
#include "StdAfx.h" #include "Caller.h" #include <assert.h> CCaller::CCaller(AddFunc f): m_addFunc(f) { } CCaller::~CCaller(void) { } void CCaller::DoSomething() { assert(m_addFunc != NULL); for (int i = 1; i < 5; i++) { printf("%d + %d = %d\n",i,m_addFunc(i,i)); } }
CMathLib.h,.cpp
头文件:
#pragma once class CMathLib { public: CMathLib(void); ~CMathLib(void); static int Add(int a,int b); private: void DoSomthingElse(int a,int b); };
实现文件
#include "StdAfx.h" #include "MathLib.h" CMathLib* m_lib; CMathLib::CMathLib(void) { m_lib = this; } CMathLib::~CMathLib(void) { } void CMathLib::DoSomthingElse(int a,int b) { printf("CMathLib::DoSomethingElse(),a = %d,b = %d\n",a,b); } int CMathLib::Add(int a,int b) { m_lib->DoSomthingElse(a,b); return a + b; }
这里特别让静态成员函数范围了类的非静态函数。——参见后面的背景说明。
运行结果
CMathLib::DoSomethingElse(),a = 1,b = 1 1 + 1 = 2 CMathLib::DoSomethingElse(),a = 2,b = 2 2 + 2 = 4 CMathLib::DoSomethingElse(),a = 3,b = 3 3 + 3 = 6 CMathLib::DoSomethingElse(),a = 4,b = 4 4 + 4 = 8 请按任意键继续. . .
示例背景说明
假定有一个对话框应用程序,其中对话框类是A;另外有一些算法代码,比如class B。A需要调用B的算法(成员函数),而B在算法处理时,一些异常需要通过UI反馈给User。
方法X
对话框A定义一个非静态成员函数,如Notify(const char* msg); 这个函数通过DDX等机制把msg更新到用户界面上。
算法B调用A的这个Notify(),把算法处理中的异常通过A反馈给用户。
在这个过程中,A要调用算法B,所以A会依赖B;B需要通过A把消息反馈给UI,所以B依赖于A。如此,形成了循环依赖。
当然,即便循环依赖,编译&链接都是没有问题,功能也是没有问题。
不过我们不喜欢循环依赖,所以要考虑另一种方法。
方法Y
其中一种方法就是增加一个中间层,让对话框和算法类都依赖于这个中间层,姑且称为这个中间层为class C。从职责驱动的角度来分析:
- 算法B要输出异常消息的时候,调用C,由C去完成显示消息的职责;
- 对话框A只是调用算法B,去完成某种算法。
- 而真正的显示消息,只能是A。所以需要把A和C关联起来,且根据前面讨论的不希望出现循环依赖的情况下,是要A调用C,而不是C调用A。
- 其中一直方法,就是A把自己的显示消息的一个接口告诉C;C需要显示消息的时候,直接回调A定义的这个接口即可。至于A真正显示消息的处理流程,C不希望关心。
- 所以,C是一个很轻量级的中间层,A和B都依赖C;C显然不依赖于B;C看起来不依赖A,至少从形式上如此。这个形式化解耦或去除循环依赖就是通过回调。
当然,A也可以把回调接口告诉算法B,似乎可以简化上面的流程。不过,在未来A调用了新的算法D、算法E等等的时候,会发现单独另一个中间层C是较好的方式。
下面是部分代码。
以下X和Y中Notifier的名字取得不好,其实应该取个被动含义的词语。为了省事,代码暂维持不变。
对话框效果
对话框资源
IDD_FUNCPOINTERMFC_DIALOG DIALOGEX 0,221,118 STYLE DS_SETFONT | DS_MODALFRAME | DS_FIXEDSYS | WS_POPUP | WS_VISIBLE | WS_CAPTION | WS_SYSMENU EXSTYLE WS_EX_APPWINDOW CAPTION "FuncPointerMfc" FONT 8,"MS Shell Dlg",0x1 BEGIN LTEXT "Calulate: a / b",IDC_STATIC,32,15,139,10 LTEXT "a:",46,12,SS_CENTERIMAGE EDITTEXT IDC_A,64,51,ES_AUTOHSCROLL LTEXT "b:",131,33,SS_CENTERIMAGE EDITTEXT IDC_B,143,ES_AUTOHSCROLL LTEXT "Result:",31,53,28,SS_CENTERIMAGE EDITTEXT IDC_RESULT,66,ES_AUTOHSCROLL PUSHBUTTON "Calculate",IDC_CALCULATE,69,95,18 EDITTEXT IDC_MSG,25,91,182,20,ES_AUTOHSCROLL END
对话框的.h,.cpp
//.h public: afx_msg void OnBnClickedCalculate(); void InnerNotify(const char *msg); static void Notify(const char *msg); private: int m_a; int m_b; int m_result; CString m_msg; CMathLib m_mathLib; //.cpp static CFuncPointerMfcDlg *m_dlg = NULL; CFuncPointerMfcDlg::CFuncPointerMfcDlg(CWnd* pParent /*=NULL*/) : CDialogEx(CFuncPointerMfcDlg::IDD,pParent),m_a(0),m_b(0),m_result(0),m_msg(_T("")) { m_hIcon = AfxGetApp()->LoadIcon(IDR_MAINFRAME); } void CFuncPointerMfcDlg::DoDataExchange(CDataExchange* pDX) { CDialogEx::DoDataExchange(pDX); DDX_Text(pDX,IDC_A,m_a); DDX_Text(pDX,IDC_B,m_b); DDX_Text(pDX,IDC_RESULT,m_result); DDX_Text(pDX,IDC_MSG,m_msg); } BEGIN_MESSAGE_MAP(CFuncPointerMfcDlg,CDialogEx) ON_WM_SYSCOMMAND() ON_WM_PAINT() ON_WM_QUERYDRAGICON() ON_BN_CLICKED(IDC_CALCULATE,&CFuncPointerMfcDlg::OnBnClickedCalculate) END_MESSAGE_MAP() // CFuncPointerMfcDlg message handlers BOOL CFuncPointerMfcDlg::OnInitDialog() { CDialogEx::OnInitDialog(); // ........... // TODO: Add extra initialization here m_dlg = this; m_mathLib.SetNotifier(&CFuncPointerMfcDlg::Notify); return TRUE; // return TRUE unless you set the focus to a control } void CFuncPointerMfcDlg::InnerNotify(const char *msg) { m_msg = msg; UpdateData(FALSE); } void CFuncPointerMfcDlg::Notify(const char *msg) { m_dlg->InnerNotify(msg); } void CFuncPointerMfcDlg::OnBnClickedCalculate() { UpdateData(TRUE); m_result = m_mathLib.Mod(m_a,m_b); UpdateData(FALSE); }
MathLib.h
#pragma once typedef void (*Notify)(const char *msg); class CMathLib { public: CMathLib(void); ~CMathLib(void); int Mod(int a,int b); void SetNotifier(Notify notify); private: Notify m_notify; };
MathLib.cpp
#include "StdAfx.h" #include "MathLib.h" static void NullNotify(const char *msg) { // do nothing } CMathLib::CMathLib(void): m_notify(NullNotify) { } CMathLib::~CMathLib(void) { } int CMathLib::Mod(int a,int b) { const int DEFAULT_RESULT = 0; if (0 == b) { m_notify("Oooh,b is zero!"); return DEFAULT_RESULT; } return a / b; } void CMathLib::SetNotifier(Notify notify) { m_notify = notify; }
方法Z
直接上代码。对话框资源一样,把Nofity接口化处理。
CNotifierInterface
头文件:
#pragma once class CNotifierInterface { public: CNotifierInterface(void); virtual ~CNotifierInterface(void); virtual void Notify(const char *format,...) = 0; };
实现文件:
#include "StdAfx.h" #include "NotifierInterface.h" CNotifierInterface::CNotifierInterface(void) { } CNotifierInterface::~CNotifierInterface(void) { }
CNullNotifier
头文件:
#pragma once #include "NotifierInterface.h" class CNullNotifier: public CNotifierInterface { public: CNullNotifier(void); virtual ~CNullNotifier(void); void Notify(const char *format,...); };
实现文件:
#include "StdAfx.h" #include "NullNotifier.h" CNullNotifier::CNullNotifier(void) { } CNullNotifier::~CNullNotifier(void) { } void CNullNotifier::Notify(const char *format,...) { // do nothing }
CMathLib
头文件:
#pragma once class CNotifierInterface; class CMathLib { public: CMathLib(void); ~CMathLib(void); int Mod(int a,int b); void SetNotifier(CNotifierInterface *pNotifier); private: CNotifierInterface *m_pNotifier; };
实现文件:
#include "StdAfx.h" #include "MathLib.h" #include "NotifierInterface.h" #include "NullNotifier.h" static CNullNotifier m_nullNotifier; CMathLib::CMathLib(void): m_pNotifier(&m_nullNotifier) { } CMathLib::~CMathLib(void) { } int CMathLib::Mod(int a,int b) { const int DEFAULT_RESULT = 0; if (0 == b) { m_pNotifier->Notify("Oooh,b is zero!"); return DEFAULT_RESULT; } return a / b; } void CMathLib::SetNotifier(CNotifierInterface *pNotifier) { if (m_pNotifier) m_pNotifier = pNotifier; }
对话框类
仅提供部分代码,省略掉MFC框架自动生成的代码,以及上一节已经给出的消息映射等代码。
头文件:
#pragma once #include "MathLib.h" #include "NotifierInterface.h" class CFuncPointerMfcDlg : public CDialogEx,public CNotifierInterface { //.............. public: afx_msg void OnBnClickedCalculate(); void Notify(const char *format,...); private: int m_a; int m_b; int m_result; CString m_msg; CMathLib m_mathLib; };
实现文件:
#include "stdafx.h" #include "FuncPointerMfc.h" #include "FuncPointerMfcDlg.h" #include "afxdialogex.h" BOOL CFuncPointerMfcDlg::OnInitDialog() { CDialogEx::OnInitDialog(); // .............. // TODO: Add extra initialization here m_mathLib.SetNotifier(this); return TRUE; // return TRUE unless you set the focus to a control } void CFuncPointerMfcDlg::Notify(const char *format,...) { m_msg = format; //此处简化处理。。。 UpdateData(FALSE); } void CFuncPointerMfcDlg::OnBnClickedCalculate() { UpdateData(TRUE); m_msg.Empty(); m_result = m_mathLib.Mod(m_a,m_b); UpdateData(FALSE); }
functor
如果愿意,在方法Z中,CMathLib中的如下调用可以改为functor:
m_pNotifier->Notify("Oooh,b is zero!");
方法就是把所有的Notify()函数改成operator():
//void Notify(const char *format,...); void operator()(const char *format,...);
然后在调用的时候如下:
(*m_pNotifier)("Oooh,b is zero!");
如此,代码看起来会简洁一些。
方法Z+
再改为Observer模式。之前已有的Notifer相关的class均移除掉。完整的代码如下:
CStringWrapper
这个class参考:可变长参数&日期等
头文件:
#pragma once class CStringWrapper { public: static CString Wrap(const char *format,...); };
实现文件:
#include "StdAfx.h" #include "StringWrapper.h" CString CStringWrapper::Wrap(const char *format,...) { const size_t BUFFER_MAX_SIZE = 1024; static TCHAR buffer[BUFFER_MAX_SIZE]; va_list ap; va_start(ap,format); vsprintf(buffer,format,ap); va_end (ap); return CString(buffer); }
CMsgSubject
头文件:
#pragma once class CMsgObserver; class CMsgSubject { public: CMsgSubject(void); virtual ~CMsgSubject(void); void AssignObserver(CMsgObserver *observer); virtual void Notify(const CString &msg); private: CMsgObserver *m_pObserver; };
实现文件:
#include "StdAfx.h" #include "MsgSubject.h" #include "NullMsgObserver.h" // Avoid to use new(). static CNullMsgObserver m_nullMsgObserver; CMsgSubject::CMsgSubject(void) : m_pObserver(&m_nullMsgObserver) { } CMsgSubject::~CMsgSubject(void) { } void CMsgSubject::AssignObserver(CMsgObserver *observer) { if (observer != NULL) m_pObserver = observer; } void CMsgSubject::Notify(const CString &msg) { m_pObserver->Update(msg); }
CMsgObserver
头文件:
#pragma once class CMsgObserver { public: CMsgObserver(void); virtual ~CMsgObserver(void); virtual void Update(const CString &msg) = 0; };
实现文件:
#include "StdAfx.h" #include "MsgObserver.h" CMsgObserver::CMsgObserver(void) { } CMsgObserver::~CMsgObserver(void) { }
CMathLib
由于抽象出了一个CMsgSubject,所以现在的CMathLib的职责就简单多了。
头文件:
#pragma once #include "MsgSubject.h" class CMathLib: public CMsgSubject { public: CMathLib(void); ~CMathLib(void); int Mod(int a,int b); };
实现文件:
#include "StdAfx.h" #include "MathLib.h" #include "StringWrapper.h" CMathLib::CMathLib(void) { } CMathLib::~CMathLib(void) { } int CMathLib::Mod(int a,int b) { const int DEFAULT_RESULT = 0; if (0 == b) { Notify("Oooh,b is zero!"); return DEFAULT_RESULT; } CString msg(CStringWrapper::Wrap("%d / %d = %d",b,a / b)); Notify(msg); return a / b; }
CNullMsgObserver
头文件:
#pragma once #include "MsgObserver.h" class CNullMsgObserver : public CMsgObserver { public: CNullMsgObserver(void); virtual ~CNullMsgObserver(void); void Update(const CString &msg); };
实现文件:
#include "StdAfx.h" #include "NullMsgObserver.h" CNullMsgObserver::CNullMsgObserver(void) { } CNullMsgObserver::~CNullMsgObserver(void) { } void CNullMsgObserver::Update(const CString &msg) { // do nothing }
对话框类
头文件:
// FuncPointerMfcDlg.h : header file // #pragma once #include "MathLib.h" #include "MsgObserver.h" // CFuncPointerMfcDlg dialog class CFuncPointerMfcDlg : public CDialogEx,public CMsgObserver { // Construction public: CFuncPointerMfcDlg(CWnd* pParent = NULL); // standard constructor // Dialog Data enum { IDD = IDD_FUNCPOINTERMFC_DIALOG }; protected: virtual void DoDataExchange(CDataExchange* pDX); // DDX/DDV support // Implementation protected: HICON m_hIcon; // Generated message map functions virtual BOOL OnInitDialog(); afx_msg void OnSysCommand(UINT nID,LPARAM lParam); afx_msg void OnPaint(); afx_msg HCURSOR OnQueryDragIcon(); DECLARE_MESSAGE_MAP() public: afx_msg void OnBnClickedCalculate(); void Update(const CString &msg); private: int m_a; int m_b; int m_result; CString m_msg; CMathLib m_mathLib; };
实现文件:
// FuncPointerMfcDlg.cpp : implementation file // CFuncPointerMfcDlg::CFuncPointerMfcDlg(CWnd* pParent /*=NULL*/) : CDialogEx(CFuncPointerMfcDlg::IDD,&CFuncPointerMfcDlg::OnBnClickedCalculate) END_MESSAGE_MAP() // CFuncPointerMfcDlg message handlers BOOL CFuncPointerMfcDlg::OnInitDialog() { CDialogEx::OnInitDialog(); // ......... // TODO: Add extra initialization here m_mathLib.AssignObserver(this); return TRUE; // return TRUE unless you set the focus to a control } void CFuncPointerMfcDlg::Update(const CString &msg) { m_msg = msg; UpdateData(FALSE); } void CFuncPointerMfcDlg::OnBnClickedCalculate() { UpdateData(TRUE); m_msg.Empty(); m_result = m_mathLib.Mod(m_a,m_b); UpdateData(FALSE); }