Modern C++ design 第5章 Command 设计模式
2013-10-24 20:49
453 查看
Modern C++ design 第5章 Command 设计模式
命令模式将发出命令的责任和执行命令的责任分离开来Command模式两个特点
接口分类;
时间分离。Command保存了一个整装待发的处理请求,供将来运用。Command模式中,收集“某处理动作所需环境”的时刻和执行该动作的时刻并不相同。
可以将Command分为两类
1.转发式命令forwarding command
2.主动式命令active command
C++中支持operator()的所有构件:
1. C-like function(C风格函数)
2. C-like pointer to function(C风格的函数指针)
3. reference to function,指向函数的引用
4. functor(仿函数),即自行定义了operator()的一种对象
5. operator.*或operator->*的施行结果。
6. construct call(构造函数)
设计思想,首先思考下对函数指针的封装,要做到通用,那就必须知道传入的函数指针的类型,应该要让编译器自动推导出来,而类模板是不允许推导的,只有函数可以进行参数推导,故在设计类的时候,可以在构造函数中使用模板推导,而要做到对operator()的封装,那必须传入返回值和参数,这两个模板参数,于是便有了如下的声明:
template <typename R = void, class TList = NullType> class Functor { template <typename Fun> Functor(Fun fun) {}// 此处通过函数模板自动推导功能,获得了传入的fun的型别Fun };
然后里面的构造函数需要推导出类型,然后在传入后,需要将fun即函数指针保存下来,需要有个类对fun进行一个封装,此时需要传入R(返回值),TList(参数列表),Fun(指针类型),其中R和TList都是Functor的,通过Fun我们可以得到指针类型。
基于以上的分析我们可以得到如下的代码:
template <typename R, class TList, typename Fun> class FunctorHandler { public: FunctorHandler(Fun const & fun ) : fun_(fun) { } private: Fun fun_; }; template <typename R = void, class TList = NullType> class Functor { public: //typedef FunctorImpl<R,TList> Impl; typedef FunctorImpl<R,TList,Fun> Impl; typedef R ResultType; typedef TList ParamList; public: //R operator() (P1); template <typename Fun> Functor(Fun fun) : spImpl_(new FunctorHandler<R,TList,Fun>(fun)) { } private: std::auto_ptr<Impl> spImpl_; };
但上面是有问题的,在想要new FunctorHandler的时候,返回一个指针,此时想要保存下这个指针,但是此时FunctorHandler的类型却无法表示出来,因为实例化的时候需要Fun类型,但是这个类型却是在构造函数的时候才推导出来的,所以在别处无法使用,此时我们就必须要在实例化的时候去掉Fun类型,这个怎么办呢?此时我们可以通过C++中的构造,声明一个抽象类,它没有具体的实现,只是提供一个一些接口,然后让子类去具体实现,根据以上的想法,我们就可以设计如下的代码:
// 基类 template <typename R, class TList> class FunctorImpl; // 无参数的实现 template <typename R> class FunctorImpl<R, NullType> { public: typedef R ResultType; virtual R operator() () = 0; virtual ~FunctorImpl() {} }; template <class ParentFunctor, typename Fun> class FunctorHandler : public ParentFunctor::Impl { typedef typename ParentFunctor::Impl Base; public: FunctorHandler( Fun const & fun ) : fun_(fun) { } private: Fun fun_; }; template <typename R = void, class TList = NullType> class Functor { public: typedef FunctorImpl<R,TList> Impl; //typedef FunctorImpl<R,TList,Fun> Impl; typedef R ResultType; typedef TList ParamList; public: //R operator() (P1); template <typename Fun> //Functor(Fun fun) : spImpl_(new FunctorHandler<R,TList,Fun>(fun)) Functor(Fun fun) : spImpl_(new FunctorHandler<Functor,Fun>(fun)) { } private: std::auto_ptr<Impl> spImpl_; };
上面的程序只要再实现下operator()操作,并且无参数情况下,就可以有个简单的实现了,下面是完整的实现代码,代码放在git中了。
现在再设计使其支持一个参数,代码如下:
template <typename R, typename P1> class FunctorImpl<R, LOKI_TYPELIST_1(P1)> { public: typedef R ResultType; typedef P1 Param1; virtual ResultType operator() (Param1) = 0; virtual ~FunctorImpl() {} }; template <class ParentFunctor, typename Fun> class FunctorHandler : public ParentFunctor::Impl { typedef typename ParentFunctor::Impl Base; typedef typename Base::ResultType ResultType; typedef typename Base::Param1 Param1; public: FunctorHandler( Fun const & fun ) : fun_(fun) { } ResultType operator() () { return fun_(); } ResultType operator() (Param1 p1) { return fun_(p1); } private: Fun fun_; }; template <typename R = void, class TList = NullType> class Functor { public: typedef FunctorImpl<R,TList> Impl; //typedef FunctorImpl<R,TList,Fun> Impl; typedef R ResultType; typedef TList ParamList; typedef typename Impl::Param1 Param1; public: //R operator() (P1); template <typename Fun> //Functor(Fun fun) : spImpl_(new FunctorHandler<R,TList,Fun>(fun)) Functor(Fun fun) : spImpl_(new FunctorHandler<Functor,Fun>(fun)) { } ResultType operator() () { return (*spImpl_)(); } ResultType operator() (Param1 p1) { return (*spImpl_)(p1); } private: std::auto_ptr<Impl> spImpl_; };
用这种方法设计的时候,会带来问题,无法实例化,因为在FunctorImpl中不是都有参数Param1和Param2,解决办法是设计一个基类,在基类中定义,然后子类中再去覆盖掉,具体实现如下:
#pragma once #include <memory> #include <loki/Typelist.h> // #include <loki/Sequence.h> class NullType { }; class EmptyType { }; inline bool operator==(EmptyType const & lhs, EmptyType const & rhs) { return true; } inline bool operator<(EmptyType const & lhs, EmptyType const & rhs) { return false; } inline bool operator>(EmptyType const & lhs, EmptyType const & rhs) { return false; } #define ParamDefine(NUM) typedef EmptyType Param##NUM template <typename R> class FunctorImplBase { public: typedef R ResultType; typedef FunctorImplBase<R> FunctorImplBaseType; //typedef EmptyType Param1; ParamDefine(1); ParamDefine(2); // ParamDefine(3); // ParamDefine(4); // ParamDefine(5); // ParamDefine(6); // ParamDefine(7); // ParamDefine(8); virtual ~FunctorImplBase() {} }; template <typename R, class TList> class FunctorImpl;
上面这已经能够实现了,其他对于Functor的值语义的实现,通过复制构造,赋值构造来实现。
接着在继续添加成员函数指针的功能,如下:
template <class ParentFunctor, typename PointerToObj, typename PointerToMemFn> class MemFunHandler : public ParentFunctor::Impl { typedef typename ParentFunctor::Impl Base; typedef typename Base::ResultType ResultType; typedef typename Base::Param1 Param1; typedef typename Base::Param2 Param2; public: //MemFunHandler(PointerToObj const & pObj, PointerToMemFn & pMenFn) MemFunHandler(PointerToObj const & pObj, PointerToMemFn pMenFn) : pObj_(pObj) , pMemFn_(pMenFn) { } ResultType operator() () { return ((*pObj_).*pMemFn_)(); } ResultType operator() (Param1 p1) { return ((*pObj_).*pMemFn_)(p1); } ResultType operator() (Param1 p1,Param2 p2) { ((*pObj_).*pMemFn_)(p1,p2); } private: PointerToObj pObj_; PointerToMemFn pMemFn_; };
现在讨论几个上面的设计细节
1.转发函数的成本
当参数ResultType operator()(Param1 p1,Param2 p2) { ((*pObj_).*pMemFn_)(p1,p2); }
对于上面这个式子,如果Param1和Param2的复制成本高的话,是会带来效率影响的,那怎么减少转发成本呢?可以通过ResultType
operator() (Param1 &p1,Param2 &p2)设置为引用,但此处会带来问题,什么问题呢?
如果此处设置Param1为string&,那么p1的型别将会变成string&&,这显然不合理,所以我们需要一个工具,在传递过程中使用正确的型别进行传递,这个可以参考第二章的TypeTraits。
下面进入下一个议题,绑定问题
绑定有什么作用呢?绑定能够将一定的(context)保存下来,使得调用的时候能够不仅呈现操作意图,还能够体现当时操作的环境对操作的影响。
绑定怎么处理?绑定需要根据传入的Functor类型来进行处理,传经来的Functor类型有个Traits,可以通过这个类进行处理,而需要根据传递进来的参数来推导Functor类型,则必须是一个函数模板。根据以上的假设,然后要需要注意的一个问题是在设计Functor的构造函数的时候,要补上复制构造函数和对aoto_ptr的构造函数,而怎么保证不同子类在复制的时候得够得到确定的类别,可以通过在基类中设置一个虚函数DoClone()来实现,子类通过实现这个函数,从而能够实现不同的子类的复制工作。关键代码如下:
virtual FunctorImplBase * DoClone() const = 0; template <class U> static U * Clone( U* pObj ) { if ( !pObj ) return 0; U * pClone = static_cast<U*>(pObj->DoClone()); assert( typeid(pClone) == typeid(pObj) ); return pClone; }
补充:
对clone的补充,下面重新设计一个代码来进行测试:
#pragma once #include <iostream> #include <memory> class Animal { public: virtual void Speak() = 0; virtual Animal * DoClone() const = 0; virtual ~Animal() { } }; #define DEFINE_CLONE_ANIMAL(Cls) \ virtual Cls * DoClone() const { return new Cls(*this); } class Bird : public Animal { public: virtual void Speak() { std::cout << "I am a bird.\n"; } DEFINE_CLONE_ANIMAL(Bird); }; class Fish : public Animal { public: virtual void Speak() { std::cout << "I am a fish.\n"; } DEFINE_CLONE_ANIMAL(Fish); }; class AnimalHolder { public: AnimalHolder(AnimalHolder const &rhs) : spImpl_( rhs.spImpl_->DoClone() ) {} AnimalHolder(Animal * spImpl ) : spImpl_( std::auto_ptr<Animal>(spImpl) ) { } void Speak() { spImpl_->Speak(); } private: std::auto_ptr<Animal> spImpl_; };
相关文章推荐
- 读-《c++设计新思维-泛型编程与设计模式之应用》经典记录(英文书名:《modern c++ design》)
- 读-《c++设计新思维-泛型编程与设计模式之应用》经典记录(英文书名:《modern c++ design》)
- 我所理解的设计模式(C++实现)——命令模式(Command Pattern)
- 设计模式Command的C++实现源码
- C++设计模式-Command
- Head First 设计模式 (六) 命令模式(Command pattern) C++实现
- 设计模式(C++实现)之Command
- (Boolan)C++设计模式 <十二> ——命令模式(Command)和访问器(Visitor)
- 设计模式C++描述----19.命令(Command)模式
- 设计模式C++学习笔记之十二(Command命令模式)
- C++设计模式之十四:Command(命令)
- Head First 设计模式 (六) 命令模式(Command pattern) C++实现
- Design Pattern Command 命令设计模式
- C++设计模式10--命令模式(二)(Command)--降低请求发送者与接收者耦合
- 8.2.2 命令设计模式(The command design pattern)
- [C++设计模式] command 命令模式
- C++设计模式-Command命令模式
- 设计模式系列3-----C++实现命令模式(Command Pattern)
- c++ 设计模式之命令(Command)模式
- API Design for C++之Pimpl设计模式