Improved command mode through c++11

  • 2020-04-02 01:54:47
  • OfStack

The pattern is subtle but not perfect, such as the observer life cycle problem in the observer pattern; Such as the problem of circular dependencies in the visitor pattern; Many other patterns suffer from limitations such as limited usage scenarios, complex implementations, lack of simplicity, and lack of generality. However, I think most of the shortcomings can be remedied and improved by some methods, such as using the new features of c++11. Hence the c++11 series to improve our patterns. This time I'm going to show you how to use c++11 to improve the command pattern. About command mode

The command pattern encapsulates the request as an object, decouples the originator and performer of the request, and supports queuing requests, undo, and redo. Its class diagram is as follows:

< img Alt = "" border = 0 SRC =" / / files.jb51.net/file_images/article/201311/201311101152232.jpg ">
By encapsulating the requests into command objects, we can centralize or defer the processing of these command requests, and the commands can be Shared by different client objects, as well as control the priority of requests, queuing, support for request command undo, redo, and so on. These benefits of the command pattern are obvious, but its problems are exposed in practice. As the number of requests increases, so does the number of command classes that encapsulate them, especially in GUI applications. More and more command classes cause class explosions that are difficult to manage. About this class explosion, GOF have long realized that they put forward a solution: for simple command cannot cancel and don't need parameters, can use a command class template to the parameterized the command receiver, to parameterized command classes with the receiver type, and maintain a binding between the receiver object and an action, and this action is to use the pointer to the same member function of storage. The specific code is like this:
Simple command class definition:

< img Alt = "" border = 0 SRC =" / / files.jb51.net/file_images/article/201311/201311101152233.jpg ">

The constructor stores the behavior in the receiver and the corresponding instance variable. The Execute operation executes the action of the receiver.

< img Alt = "" border = 0 SRC =" / / files.jb51.net/file_images/article/201311/201311101152234.jpg ">

To create a Command object that invokes the Action Action on an instance of the MyClass class, you simply need the following code:

< img Alt = "" border = 0 SRC =" / / files.jb51.net/file_images/article/201311/201311101152235.jpg ">

A generic simple command class is a good way to avoid constantly creating new command classes. However, this method is not perfect, that is, it can only be a simple command class, can not generalize to complex, even all command classes, which is its defect, so it only partially solves the problem. I think I can improve the flaw of this method and solve the problem of explosion like perfectly. I don't know if anyone solved this problem before c++11, at least I didn't see it. Now you can solve this problem with c++11.

C++ 11 improves command mode

The key to the perfect solution to the command mode class explosion problem is to define a generic generic command class that generalizes all commands instead of the simple ones mentioned by the GOF. We'll come back to see that simple commands in the GOF class definition, it is just a generalization has no parameters and return values of command, the command class internal references to a function pointer, the receiver and the receiver if the receiver's behavior function Pointers have parameters cannot be universal, so we have to solve the key problem is how to make the command class can accept all the member function pointer or function object.
Is there a class that accepts all member functions, regular functions, and function objects? Yes, you can in c++11. In my last post, I mentioned a versatile function wrapper that accepts all function objects, functions, and lamda expressions. No, because it's not perfect, it doesn't accept member functions yet, so it's not really a universal function wrapper. I'm going to expand on that and make it a really versatile functional wrapper.

Accept a wrapper for function, function object, lamda, and normal functions:


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

A wrapper that accepts a member function:


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

Let it receive member functions by overloading Wrap. This is the end of a truly versatile functional wrapper. Now let's see how it applies to the command mode to solve the class explosion problem perfectly.

A generic command class:


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

Test code:


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

We defined a universal function wrapper in the general command class, so that we can encapsulate all the commands, add new requests do not need to redefine the command, the perfect solution to the problem of command class explosion.


Related articles: