通过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语言实现括号配对的方法示例

    C语言实现括号配对的方法示例

    本文主要介绍了C语言实现括号配对的方法示例,文中通过示例代码介绍的非常详细,具有一定的参考价值,感兴趣的小伙伴们可以参考一下
    2021-09-09
  • C++深入刨析muduo中的抽象类Poller

    C++深入刨析muduo中的抽象类Poller

    muduo网络库中Poller类是一个抽象类,用户使用PollPoller或者EPollPoller类,下面跟随小编一起来详细了解一下
    2022-04-04
  • 使用VC6.0对C语言程序进行调试的基本手段分享

    使用VC6.0对C语言程序进行调试的基本手段分享

    这篇文章主要介绍了用VC6.0开发c语言程序的时候调试代码的一些小技巧,需要的朋友可以参考下
    2013-07-07
  • C++ Clock类模拟实现闹钟运行

    C++ Clock类模拟实现闹钟运行

    这篇文章主要为大家详细介绍了C++ Clock类模拟实现闹钟运行,文中示例代码介绍的非常详细,具有一定的参考价值,感兴趣的小伙伴们可以参考一下
    2022-03-03
  • 应用程序操作NorFlash示例代码分享(norflash接口使用方法)

    应用程序操作NorFlash示例代码分享(norflash接口使用方法)

    相对于操作NandFlash,操作NorFlash相对简单,因为基本不需要考虑坏块,NorFlash也没有OOB区域,也跟ECC没有关系。读写擦除相对容易,下面看个例子吧
    2013-12-12
  • C/C++指针介绍与使用详解

    C/C++指针介绍与使用详解

    不知从何时起对你一眼万年,从此,每一天被赋予了特别的意义。时隔多年,依然挥之不去是你------指针!!!本篇中几乎数据类型只用了int,但是float、double等也是可以的
    2022-08-08
  • QT实现简单五子棋游戏

    QT实现简单五子棋游戏

    这篇文章主要为大家详细介绍了QT实现简单五子棋游戏,文中示例代码介绍的非常详细,具有一定的参考价值,感兴趣的小伙伴们可以参考一下
    2021-05-05
  • C语言中栈的两种实现方法

    C语言中栈的两种实现方法

    栈的实现方式有两种,一种方法是使用指针,而另一种方法则是使用数组,这篇文章主要介绍了C语言中栈的两种实现方法,需要的朋友可以参考下
    2021-08-08
  • C++一个函数如何调用其他.cpp文件中的函数

    C++一个函数如何调用其他.cpp文件中的函数

    这篇文章主要介绍了C++一个函数如何调用其他.cpp文件中的函数问题,具有很好的参考价值,希望对大家有所帮助。如有错误或未考虑完全的地方,望不吝赐教
    2023-02-02
  • 详解C++编程中向函数传递引用参数的用法

    详解C++编程中向函数传递引用参数的用法

    这篇文章主要介绍了详解C++编程中向函数传递引用参数的用法,包括使函数返回引用类型以及对指针的引用,需要的朋友可以参考下
    2016-01-01

最新评论