通过c++11改进我们的模式之改进命令模式

 更新时间:2013年11月10日 10:13:59   作者:  
这篇我要讲的是如何使用c++11改进命令模式,感兴趣的朋友可以看下

模式虽然精妙,却难完美,比如观察者模式中观察者生命周期的问题;比如访问者模式中循环依赖的问题等等;其它很多模式也存在这样那样的一些不足之处,如使用场景受限、实现复杂、不够简洁、不够通用等。但我觉得不足之处大都是可以采取一些手法去弥补去改进的,比如用c++11的新特性来改进。因此,便有了c++11改进我们的模式这个系列。这次我要讲的是如何使用c++11改进命令模式。关于命令模式

  命令模式的作用是将请求封装为一个对象,将请求的发起者和执行者解耦,支持对请求排队以及撤销和重做。它的类图如下:


  由于将请求都封装成一个个命令对象了,使得我们可以集中处理或者延迟处理这些命令请求,而且不同的客户对象可以共享这些命令,还可以控制请求的优先级、排队、支持请求命令撤销和重做等等。命令模式的这些好处是显而易见的,但是,在实际使用过程中它的问题也暴露出来了。随着请求的增多,请求的封装类--命令类也会越来越多,尤其是GUI应用中,请求是非常多的。越来越多的命令类会导致类爆炸,难以管理。关于类爆炸这个问题,GOF很早就意识到了,他们提出了一个解决方法:对于简单的不能取消和不需要参数的命令,可以用一个命令类模板来参数化该命令的接收者,用接收者类型来参数化命令类,并维护一个接收者对象和一个动作之间的绑定,而这一动作是用指向同一个成员函数的指针存储的。具体代码是这样的:
简单命令类的定义:

构造器存储接收者和对应实例变量中行为。Execute操作实施接收者的这个动作。

为创建一个调用MyClass类的一个实例上的Action行为的Command对象,仅需要如下代码:

  通过一个泛型的简单命令类来避免不断创建新的命令类,是一个不错的办法,但是,这个办法不完美,即它只能是简单的命令类,不能对复杂的,甚至所有的命令类泛化,这是它的缺陷,所以,它只是部分的解决了问题。我想我可以改进这个办法缺陷,完美的解决类爆炸的问题。在c++11之前我不知道有没有人解决过这个问题,至少我没看到过。现在可以通过c++11来完美的解决这个问题了。

c++11改进命令模式

  要完美的解决命令模式类爆炸问题的关键是如何定义个通用的泛化的命令类,这个命令类可以泛化所有的命令,而不是GOF提到的简单命令。我们再回过头来看看GOF中那个简单的命令类的定义,它只是泛化了没有参数和返回值的命令类,命令类内部引用了一个接收者和接收者的函数指针,如果接收者的行为函数指针有参数就不能通用了,所以我们要解决的关键问题是如何让命令类能接受所有的成员函数指针或者函数对象。
  有没有一个能接受所有成员函数、普通函数和函数对象的类呢?有,在c++11中可以有,我上一篇博文中提到了一个万能的函数包装器,它可以接受所有的函数对象、fucntion和lamda表达式,它行不行呢?不行,因为它还不够完美,它还不能接受成员函数呢,所以它还不是真正的万能的函数包装器。我打算在它的基础上再扩展一下,让它为一个真正的万能的函数包装器。

  接受function、函数对象、lamda和普通函数的包装器:

复制代码 代码如下:

template< class F, class... Args, class = typename std::enable_if<!std::is_member_function_pointer<F>::value>::type>
void Wrap(F && f, Args && ... args)
{
return f(std::forward<Args>(args)...);
}

接受成员函数的包装器:

复制代码 代码如下:

template<class R, class C, class... DArgs, class P, class... Args>
void Wrap(R(C::*f)(DArgs...), P && p, Args && ... args)
{
return (*p.*f)(std::forward<Args>(args)...);
}

  通过重载的Wrap让它能接收成员函数。这样一个真正意义上的万能的函数包装器就完成了。现在再来看,它是如何应用到命令模式中,完美的解决类爆炸的问题。

  一个通用的泛化的命令类:

复制代码 代码如下:

template<typename R=void>
struct CommCommand
{
private:
std::function < R()> m_f;

public:
template< class F, class... Args, class = typename std::enable_if<!std::is_member_function_pointer<F>::value>::type>
void Wrap(F && f, Args && ... args)
{
m_f = [&]{return f(std::forward<Args>(args)...); };
}

template<class R, class C, class... DArgs, class P, class... Args>
void Wrap(R(C::*f)(DArgs...) const, P && p, Args && ... args)
{
m_f = [&, f]{return (*p.*f)(std::forward<Args>(args)...); };
}

// non-const member function
template<class R, class C, class... DArgs, class P, class... Args>
void Wrap(R(C::*f)(DArgs...), P && p, Args && ... args)
{
m_f = [&, f]{return (*p.*f)(std::forward<Args>(args)...); };
}

R Excecute()
{
return m_f();
}
};

测试代码:

复制代码 代码如下:

struct STA
{
int m_a;
int operator()(){ return m_a; }
int operator()(int n){ return m_a + n; }
int triple0(){ return m_a * 3; }
int triple(int a){ return m_a * 3 + a; }
int triple1() const { return m_a * 3; }
const int triple2(int a) const { return m_a * 3+a; }

void triple3(){ cout << "" <<endl; }
};

int add_one(int n)
{
return n + 1;
}

void TestWrap()
{

CommCommand<int> cmd;
// free function
cmd.Wrap(add_one, 0);

// lambda function
cmd.Wrap([](int n){return n + 1; }, 1);

// functor
cmd.Wrap(bloop);
cmd.Wrap(bloop, 4);

STA t = { 10 };
int x = 3;
// member function
cmd.Wrap(&STA::triple0, &t);
cmd.Wrap(&STA::triple, &t, x);
cmd.Wrap(&STA::triple, &t, 3);

cmd.Wrap(&STA::triple2, &t, 3);
auto r = cmd.Excecute();

CommCommand<> cmd1;
cmd1.Wrap(&Bloop::triple3, &t);
cmd1.Excecute();
}

我们在通用的命令类内部定义了一个万能的函数包装器,使得我们可以封装所有的命令,增加新的请求都不需要重新定义命令了,完美的解决了命令类爆炸的问题。

相关文章

  • C++中list的使用与模拟实现

    C++中list的使用与模拟实现

    list相较于vector来说会显得复杂,它的好处是在任意位置插入,删除都是一个O(1)的时间复杂度,下面这篇文章主要给大家介绍了关于C++中list的使用与模拟实现的相关资料,需要的朋友可以参考下
    2022-05-05
  • C++算法系列之日历生成的算法代码

    C++算法系列之日历生成的算法代码

    日历算法首先要知道日历的编排规则,也就是历法。所谓历法,指的就是推算年、月、日的时间长度和它们之间的关系,指定时间序列的法则。
    2018-05-05
  • C++并查集亲戚(Relations)算法实例

    C++并查集亲戚(Relations)算法实例

    这篇文章主要介绍了C++并查集亲戚(Relations)算法,实例分析了并查集亲戚算法的原理与实现技巧,具有一定参考借鉴价值,需要的朋友可以参考下
    2015-04-04
  • C语言中的强符号和弱符号介绍

    C语言中的强符号和弱符号介绍

    这篇文章主要介绍了C语言中的强符号和弱符号介绍,本文用多个实例来讲解强符号和弱符号,需要的朋友可以参考下
    2015-03-03
  • C 语言中enum枚举的定义和使用小结

    C 语言中enum枚举的定义和使用小结

    在 C 语言里,enum(枚举)是一种用户自定义的数据类型,它能够让你创建一组具名的整数常量,下面我会从定义、使用、特性等方面详细介绍enum,感兴趣的朋友一起看看吧
    2025-04-04
  • 用C语言实现一个扫雷小游戏

    用C语言实现一个扫雷小游戏

    这篇文章主要为大家详细介绍了用C语言实现一个扫雷小游戏,文中示例代码介绍的非常详细,具有一定的参考价值,感兴趣的小伙伴们可以参考一下
    2021-09-09
  • C++浅拷贝与深拷贝及引用计数分析

    C++浅拷贝与深拷贝及引用计数分析

    这篇文章主要介绍了C++浅拷贝与深拷贝及引用计数分析的相关资料,需要的朋友可以参考下
    2017-06-06
  • C++读入XML文件示例

    C++读入XML文件示例

    本篇文章主要介绍了C++读入XML文件,读取和设置xml配置文件是最常用的操作,TinyXML是一个开源的解析XML的C++解析库,感兴趣的小伙伴们可以参考一下。
    2016-12-12
  • C语言结构体占用内存深入讲解

    C语言结构体占用内存深入讲解

    这篇文章主要给大家介绍了关于C语言结构体占用内存的相关资料,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友们下面随着小编来一起学习学习吧
    2020-12-12
  • C语言的预处理介绍

    C语言的预处理介绍

    大家好,本篇文章主要讲的是C语言的预处理介绍,感兴趣的同学赶快来看一看吧,对你有帮助的话记得收藏一下,方便下次浏览
    2021-12-12

最新评论