函数指针&回调&循环依赖

说明

描述用函数指针、回调函数来去除循环依赖的例子。

参考资料

讨论C和C++中函数指针的一个pdf文件:

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);
}

版权声明:本文内容由互联网用户自发贡献,该文观点与技术仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容, 请发送邮件至 dio@foxmail.com 举报,一经查实,本站将立刻删除。

相关推荐


什么是设计模式一套被反复使用、多数人知晓的、经过分类编目的、代码 设计经验 的总结;使用设计模式是为了 可重用 代码、让代码 更容易 被他人理解、保证代码 可靠性;设计模式使代码编制  真正工程化;设计模式使软件工程的 基石脉络, 如同大厦的结构一样;并不直接用来完成代码的编写,而是 描述 在各种不同情况下,要怎么解决问题的一种方案;能使不稳定依赖于相对稳定、具体依赖于相对抽象,避免引
单一职责原则定义(Single Responsibility Principle,SRP)一个对象应该只包含 单一的职责,并且该职责被完整地封装在一个类中。Every  Object should have  a single responsibility, and that responsibility should be entirely encapsulated by t
动态代理和CGLib代理分不清吗,看看这篇文章,写的非常好,强烈推荐。原文截图*************************************************************************************************************************原文文本************
适配器模式将一个类的接口转换成客户期望的另一个接口,使得原本接口不兼容的类可以相互合作。
策略模式定义了一系列算法族,并封装在类中,它们之间可以互相替换,此模式让算法的变化独立于使用算法的客户。
设计模式讲的是如何编写可扩展、可维护、可读的高质量代码,它是针对软件开发中经常遇到的一些设计问题,总结出来的一套通用的解决方案。
模板方法模式在一个方法中定义一个算法的骨架,而将一些步骤延迟到子类中,使得子类可以在不改变算法结构的情况下,重新定义算法中的某些步骤。
迭代器模式提供了一种方法,用于遍历集合对象中的元素,而又不暴露其内部的细节。
外观模式又叫门面模式,它提供了一个统一的(高层)接口,用来访问子系统中的一群接口,使得子系统更容易使用。
单例模式(Singleton Design Pattern)保证一个类只能有一个实例,并提供一个全局访问点。
组合模式可以将对象组合成树形结构来表示“整体-部分”的层次结构,使得客户可以用一致的方式处理个别对象和对象组合。
装饰者模式能够更灵活的,动态的给对象添加其它功能,而不需要修改任何现有的底层代码。
观察者模式(Observer Design Pattern)定义了对象之间的一对多依赖,当对象状态改变的时候,所有依赖者都会自动收到通知。
代理模式为对象提供一个代理,来控制对该对象的访问。代理模式在不改变原始类代码的情况下,通过引入代理类来给原始类附加功能。
工厂模式(Factory Design Pattern)可细分为三种,分别是简单工厂,工厂方法和抽象工厂,它们都是为了更好的创建对象。
状态模式允许对象在内部状态改变时,改变它的行为,对象看起来好像改变了它的类。
命令模式将请求封装为对象,能够支持请求的排队执行、记录日志、撤销等功能。
备忘录模式(Memento Pattern)保存一个对象的某个状态,以便在适当的时候恢复对象。备忘录模式属于行为型模式。 基本介绍 **意图:**在不破坏封装性的前提下,捕获一个对象的内部状态,并在该
顾名思义,责任链模式(Chain of Responsibility Pattern)为请求创建了一个接收者对象的链。这种模式给予请求的类型,对请求的发送者和接收者进行解耦。这种类型的设计模式属于行为
享元模式(Flyweight Pattern)(轻量级)(共享元素)主要用于减少创建对象的数量,以减少内存占用和提高性能。这种类型的设计模式属于结构型模式,它提供了减少对象数量从而改善应用所需的对象结