在个人上一篇博客 对象工厂(1)---和万恶的 switch 说再见中,咱们经过使用函数指针索引的方法,为咱们的工厂类代码中消除了 switch 语句。本篇博客的目标是将实现一个泛化的工厂类,实现代码复用。下面让咱们先分析一下在对象工厂(1)---和万恶的 switch 说再见中的工厂类的几个主要角色:c++
若是咱们使用模板编程,将上面的概念做为模板参数,那么一个泛型工厂类便呼之欲出了。但是,且慢,让咱们分析一下这4个角色是否 Factory 类都须要了解呢?答案显然不是,Concrete 子类对 Factory 彻底是透明的,Factory 在建立对象的时候,不须要也不该该关心到底有哪些具体子类(这个正是咱们上篇博客所解决的问题)。如今给出泛型工厂类的基本代码:程序员
template < class AbstractProduct, typename IdentifierType, typename ProductCreator = AbstractProduct *(*)(), > class Factory { public: bool Register(const IdentifierType &id, ProductCreator creator) { return associations_.insert(typename AssocMap::value_type(id, creator)).second; } bool Unregister(const IdentifierType &id) { return associations_.erase(id) == 1; } AbstractProduct *CreateObject(const IdentifierType &id) const { typename AssocMap::const_iterator i = associations_.find(id); if (i == associations_.end()) { // 呜呼,出现错误了,这里应该给客户端自有处理这种状况的自由 } return (i->second)(); } private: typedef std::map<IdentifierType, ProductCreator> AssocMap; AssocMap associations_; };
上面的代码为 ProductCreator 提供了一个默认的类型,一个函数指针,语法乍看有点怪异,可是若是这样写想必有点经验的 c/c++ 程序员均可以轻易认出:
typedef AbstractProduct *(*CreateFn)();
上面的代码为不接受任何参数,返回 AbstractProduct 指针的函数指针定义了一个 CreateFn 别名。接下来让咱们完善一下这个设计,如代码中的红字指出,若是一个工厂类没法识别的 type 出现了,咱们改如何处理呢?典型的处理方法有:编程
每个解决方法在不一样的场景下都是一个合适的处理方式,好比若是你须要尽早发现这种意料以外的状况,那么抛出异常是个不错的选择,若是你在开发一个网络服务器,那么显然若是直接抛出异常可能会倒置服务器 down 掉,那这时返回一个空指针,并记录错误日志多是一个更好的选择。因此咱们必须给予客户端指定如何处理的权限。这里使用的实现模式成为 Policy 模式(详见《moder c++ design》一书),这种模式将程序中一些能够有多种实现方法的地方区分出来,并抽象为一个 Policy,经过模板参数传入,使用相似组装的方法,将多个 Policy 组装成一个实用的 class,最大程度实现代码和组件的复用(其实我的感受模板编程更像是面对接口编程,只是接口不是明确写出的,而是隐藏在代码中的)。segmentfault
为了解决上面的问题,咱们引入一个 FactoryErrorPolicy,这个 Policy 负责提供一个接口,接口用于处理未知的 type,也就是说其多是这样子:服务器
template<class AbstractProduct, typename IdentifierType> class FactoryErrorPolicy { protected: (static) (static) AbstractProduct *OnUnknownType(const IdentifierType &id); };
注意代码中被括起来的 static 关键字,这并非什么晦涩的语法,而是我想在此说明,由于咱们使用的是模板编程,它是面向语法的,不是面向标记的,无论你是 static 与否,只要能够正常运行就知足咱们的接口。这个我会在以后再加以解释,如今咱们先完全完善咱们的 Factory 类:
template < class AbstractProduct, typename IdentifierType, typename ProductCreator = AbstractProduct *(*)(), template<class, class> class FactoryErrorPolicy = DefaultFactoryError > class Factory : public FactoryErrorPolicy<AbstractProduct, IdentifierType> { public: bool Register(const IdentifierType &id, ProductCreator creator) { return associations_.insert(typename AssocMap::value_type(id, creator)).second; } bool Unregister(const IdentifierType &id) { return associations_.erase(id) == 1; } AbstractProduct *CreateObject(const IdentifierType &id) const { typename AssocMap::const_iterator i = associations_.find(id); if (i == associations_.end()) { return this->OnUnknownType(id); } return (i->second)(); } private: typedef std::map<IdentifierType, ProductCreator> AssocMap; AssocMap associations_; };
注意新引入的两行代码,这里咱们为模板参数增长了一个 Policy 类,并在代码中隐式的要求
FactoryErrorPolicy<AbstractProduct, IdentifierType>
类能够提供一个 OnUnknownType() 函数接口,其能够接受 int 做为参数(它的签名能够是接受 double, char, 任何能够经过int隐式转化的类型)调用就能够了,至因而不是 static 咱们也不关心,甚至它的声明能够是这样:
void *OnUnknownType(double d, int i = 0, string="");
这是彻底能够的,在模板编程中,咱们只须要 this->OnUnknownType() 这句语句能够经过编译就能够了,这是模板编程和面向对象最大的不一样点之一,我的认为其灵活性远超面向对象编程。还要注意的是 Policy 模式每每经过定义的模板类 public 继承 Policy 类来实现。咱们甚至还为这个 Policy 提供了一个默认的 class 来减小客户端的编码成本:网络
template<class AbstractProduct, typename IdentifierType> class DefaultFactoryError { protected: static AbstractProduct *OnUnknownType(const IdentifierType &id, int i = 0, std::map<int, int> *p=NULL) { return NULL; } };
为了佐证我上面的论点,我特地选择了一个很奇怪的实现,至此咱们完整实现了一个泛型工厂类, 上一篇博客中能够很容易的复用这个类,只须要用:
typedef Factory<Shape, int> ShapeFactory;
替代以前的 ShapeFactory 实现,而且适当修改 Factory 类的调用处(由于接口名字有所改变,好比 CreateShape 改成 CreateObject),就能够直接使用现成的 Factory 类。一个通用、客户端能够轻易从新定义出现 UnKnown Type 时的行为的泛型工厂类到此就彻底实现了。再次感慨一下 c++ 模板的强大。函数
固然,上面的泛型类任然有其缺陷,好比在构造的时候没法传入参数,这对于不少没有默认构造函数的对象来讲是没法接受的,一个可能的改进方法是在 CreateObject 函数添加一个 void * 参数。具体对参数的使用逻辑从新落在每一个 Creator 中。this
AbstractPtr CreateObject(const Identifier &id, void *arg) const { typename Creators::const_iterator it = creators_.find(id); if (it == creators_.end()) { return this->OnUnknownType(id); } return (it->second)(arg); }
固然这样会致使不少不须要参数的类在注册 Creator 的时候必须使用一个可用一个 void *作参数的 Creator,增长了一点程序员的负担。笔者暂时没法想到更好的解决方法,若是有同窗能够指教的话欢迎在下面留言。鉴于自身水平有限,文章中有不免有些错误,欢迎你们指出。也但愿你们能够积极留言,与笔者一块儿讨论编程的那些事。编码