本文使用C++11改進命令模式。
原見書本[鏈接]方法不錯,但代碼有問題。於是網上搜了搜,沒想到,全和書本一模一樣,還“原創”!都沒有編譯運行過嗎?還是自己來吧!於是有本篇。
這裏也是照着書本模,但是保證代碼全部正常,備註明確。本文源碼見【完整代碼】章節,或GitHub:https://github.com/deargo/cpphelper。
命令模式
一般定義:將一個請求封裝爲一個對象,從而使你可用不同的請求對客戶進行參數化;對請求排隊或記錄請求日誌,以及支持可撤銷的操作。
由於將請求都封裝成一個個命令對象了,使得我們可以集中處理或者延遲處理這些命令請求,而且不同的客戶對象可以共享這些命令,還可以控制請求的優先級、排隊、支持請求命令撤銷和重做等等。
初步優化
命令模式的這些好處是顯而易見的,但是,在實際使用過程中它的問題也暴露出來了。隨着請求的增多,請求的封裝類--命令類也會越來越多,尤其是GUI應用中,請求是非常多的。越來越多的命令類會導致類爆炸,難以管理。
關於類爆炸這個問題,GOF很早就意識到了,他們提出了一個解決方法:對於簡單的不能取消和不需要參數的命令,可以用一個命令類模板來參數化該命令的接收者,用接收者類型來參數化命令類,並維護一個接收者對象和一個動作之間的綁定,而這一動作是用指向同一個成員函數的指針存儲的。
簡單命令類的定義:
構造器存儲接收者和對應實例變量中行爲。Execute操作實施接收者的這個動作。
爲創建一個調用MyClass類的一個實例上的Action行爲的Command對象,僅需要如下代碼:
通過一個泛型的簡單命令類來避免不斷創建新的命令類,是一個不錯的辦法,但是,這個辦法不完美,即它只能是簡單的命令類,不能對複雜的,甚至所有的命令類泛化,這是它的缺陷,所以,它只是部分的解決了問題。
C++11改進
要完美的解決命令模式類爆炸問題的關鍵是如何定義個通用的泛化的命令類,這個命令類可以泛化所有的命令,而不是GOF提到的簡單命令。
我們再回過頭來看看GOF中那個簡單的命令類的定義,它只是泛化了沒有參數和返回值的命令類,命令類內部引用了一個接收者和接收者的函數指針,如果接收者的行爲函數指針有參數就不能通用了,所以我們要解決的關鍵問題是如何讓命令類能接受所有的成員函數指針或者函數對象。
我們需要一個萬能的函數包裝器,它可以接收所有的可調用對象,包括:函數對象、成員函數、普通函數、fucntion和lamda表達式。然後將這個包裝器應用到命令模式中,從而解決類膨脹的問題。
完整代碼
#pragma once
#if _MSC_VER >= 1600
#pragma execution_character_set("utf-8")
#endif
#include <functional>
#include <type_traits>
#include <iostream>
using namespace std;
namespace CppHelper
{
template<typename Ret=void>
struct CCommand
{
private:
std::function < Ret()> m_func;
public:
//接受function、函數對象、lamda和普通函數的包裝器
template< class Func, class... Args, class = typename std::enable_if<!std::is_member_function_pointer<Func>::value>::type>
void Wrap(Func && func, Args && ... args)
{
m_func = [&]{return func(args...); };
}
//接受常量成員函數的包裝器
template<class CObj, class... DArgs, class PObj, class... Args>
void Wrap(Ret(CObj::*func)(DArgs...) const, PObj && pObj, Args && ... args)
{
m_func = [&, func]{return (*pObj.*func)( args...); };
}
//接受非常量成員函數的包裝器
template<class CObj, class... DArgs, class PObj, class... Args>
void Wrap(Ret(CObj::*func)(DArgs...), PObj && pObj, Args && ... args)
{
m_func = [&, func]{return (*pObj.*func)( args...); };
}
Ret Excecute()
{
return m_func();
}
};
}
測試代碼
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 CCommandTest()
{
TEST_FUNC_PRINT;
CppHelper::CCommand<int> cmd;
//普通函數
cmd.Wrap(add_one, 0);
cout << cmd.Excecute()<< endl;
//lambda表達式
cmd.Wrap([](int n){return n + 1; }, 1);
cout << cmd.Excecute()<< endl;
//函數對象
cmd.Wrap(STA());
cout << cmd.Excecute()<< endl;
cmd.Wrap(STA(), 4);
cout << cmd.Excecute()<< endl;
STA t = { 10 };
int x = 3;
//成員函數
cmd.Wrap(&STA::triple0, &t);
cout << cmd.Excecute()<< endl;
cmd.Wrap(&STA::triple, &t, x);
cout << cmd.Excecute()<< endl;
cmd.Wrap(&STA::triple, &t, 3);
cout << cmd.Excecute()<< endl;
//常量成員
CppHelper::CCommand<const int> cmd1;
cmd1.Wrap(&STA::triple2, &t, 3);
cout << cmd1.Excecute()<< endl;
CppHelper::CCommand<> cmd2;
cmd2.Wrap(&STA::triple3, &t);
cmd2.Excecute();
}
參考資料
參考書籍:《深入應用C++11:代碼優化與工程級應用》
參考博客:https://blog.csdn.net/weixin_34185320/article/details/85588910
另,更多源碼見GitHub:https://github.com/deargo/cpphelper。