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

时间:2023-03-02 01:58:00 

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

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

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

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

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

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

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

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

通过一个泛型的简单命令类来避免不断创建新的命令类,是一个不错的办法,但是,这个办法不完美,即它只能是简单的命令类,不能对复杂的,甚至所有的命令类泛化,这是它的缺陷,所以,它只是部分的解决了问题。我想我可以改进这个办法缺陷,完美的解决类 * 的问题。在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();
}

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

标签:命令模式
0
投稿

猜你喜欢

  • C#中Razor模板引擎简单使用

    2022-01-21 10:04:13
  • Java全面解析string类型的xml字符串

    2021-06-03 21:15:18
  • Java教程各种接口的介绍

    2022-04-29 08:49:25
  • Android使用SmsManager实现短信发送功能

    2023-08-24 17:54:20
  • Android基于Intent实现Activity之间数据传递的方法

    2022-08-01 16:34:04
  • Java如何在PDF中添加ToolTip工具提示

    2021-12-31 12:12:53
  • 详解Spring Boot 项目部署到heroku爬坑

    2021-05-28 06:21:07
  • Android 判断是否连接成功了指定wifi

    2021-10-21 04:59:10
  • Android控件之TabHost用法实例分析

    2021-07-19 10:35:21
  • java实现斗地主发牌系统

    2023-06-11 01:48:40
  • 使用C#创建Windows服务的实例代码

    2022-07-30 05:58:48
  • Java执行cmd命令的举例与注意事项

    2023-11-03 10:21:05
  • 手把手带你了解Java-Stream流方法学习及总结

    2023-11-25 19:30:15
  • 浅谈Java工程读取resources中资源文件路径的问题

    2021-07-20 19:13:45
  • Android自定义View多种效果解析

    2022-05-26 06:59:46
  • MyBatis-plus中的模糊查询解读

    2022-06-16 08:27:03
  • Android控件实现水滴效果

    2021-07-31 20:43:51
  • c#调用qq邮箱smtp发送邮件修改版代码分享

    2023-04-03 11:11:31
  • Java实战个人博客系统的实现流程

    2023-04-12 02:32:44
  • Java常用JVM参数实战

    2023-03-15 10:33:39
  • asp之家 软件编程 m.aspxhome.com