1,为什么需要对象工厂?
我们创建一个对象时,必须给出具体型别,new A也好,new B也罢。A,B都是对象的型别。我们向来反对写hardcode,但却在创建对象时必须hardcode。
如果需要根据用户的输入信息,或是网络反馈信息,或是文本文件信息来创建对象时,应该怎么办呢?
最初我们想法可能是这样的,伪代码
switch(Info) { case a: return new A; case b: return new B; ... default:... }
这就是大家都熟悉简单工厂模式,他虽然简单,但蕴含一个基本原则:根据value(a,b...)查找type(A,B...),利用type创建value(A*,B*...)。
利用对象工厂,我们创建对象时得到了解脱,无需写hardcode,不必给出类型,我们可以提供数字,字符串,以及特定格式信息来创建一个对象。
好的,这就是对象工厂存在的意义!
2.简单工厂不够完美吗?
是的,不够完美。理由有三:
它使用了switch语句,因而带有switch语句相应缺点,这是面向对象极力消除的东西(switch本身就是一种hardcode,所以面向对象:多态)
工厂类需要收集其生产的所有类型信息,编译依存性很强哦
想要添加一个新类型,需要修改工厂代码,而且通常不只一处。(引入头文件,添加常量,修改switch等)
3.我们的目标是什么?
工厂不使用switch语句
工厂可伸缩,可以动态添加或删除所生产的产品
可以对工厂提供构造对象的原料,且数量可变
泛化工厂,可以无需编码支持不同类型抽象产品
减少内存管理复杂度
尽量做到类型安全
4.看看实现,这才是重点:(在我不断完善实现的过程中,我发现自己是为了模式而模式,所以以下代码仅供参考)
struct package { void * funcSet; void * func; size_t index; string sig; }; template <typename ...> class TypeList {}; template <typename AbstractProduct ,typename IdentifierType = string> class FactoryImpl { public: template <typename... Arg> bool Register(const IdentifierType& id,const function<unique_ptr<AbstractProduct>(Arg...)>& creator) { static vector<function<unique_ptr<AbstractProduct>(Arg...)>> vf;#17 typename AssocMap::const_iterator i =associations_.find(id); if(i!= associations_.end()) return false; vf.push_back(creator); return associations_.insert(typename AssocMap::value_type(id,package {&vf,&vf.back(),vf.size()-1,string(typeid(TypeList<Arg...>).name())})).second; } template <typename ... Arg > bool UnRegister(const IdentifierType& id) { typename AssocMap::const_iterator i =associations_.find(id); if(i != associations_.end()) { assert( ((i->second).sig).compare(typeid(TypeList<Arg...>).name())==0 ); auto vf=static_cast<vector<function<unique_ptr<AbstractProduct>(Arg...)>>*>((i->second).funcSet); vf->erase(vf->begin()+(i->second).index); } return associations_.erase(id)==1; } template <typename... Arg> unique_ptr<AbstractProduct> Createobject(const IdentifierType& id,Arg&&... args) { typename AssocMap::const_iterator i =associations_.find(id); if(i != associations_.end()) { assert(((i->second).sig).compare(typeid(TypeList<Arg...>).name())==0); auto funp=static_cast<function<unique_ptr<AbstractProduct>(Arg...)>* >((i->second).func); return (*funp)(std::forward<Arg>(args)...); } assert(false); } private: typedef std::unordered_map<IdentifierType,package> AssocMap; AssocMap associations_; };
代码释疑:
17行,在干什么?是这样的,客户传过来的function对象很可能在调用时已经释放了,如果我们不保存一个function对象,仅仅将其function对象指针转变为void*,而之后我们在把void*转换为function对象指针,调用时如果function对象已经释放了,那么必然出现内存错误。
恰恰因为17行原因,一切变得非常棘手。最难实现的是UnRegister函数,这也是这段代码最大的败笔。UnRegister强迫客户显式提供正确的型别,否则不能工作。为什么一定要提供型别?因为型别擦除后,编译器已无法知道它真的型别,需要明确进行强制转换后,才能在vector中删除function对象。不然,内存泄露直至进程退出。
所有函数都加入assert,防止客户出错,提供不正确参数将导致进程终止。
代码中使用std::function和unique_ptr, std::function为不同形式的可调用体包装为相同型别,并保持统一的调用。unique_ptr则是为了方便内存管理。
1-7package结构体中 func指向用户提供function对象,funcSet指向Register函数中static vector,index为其在func指针在funcSet中位置,sig可以理解为函数签名
因为没有在类模板使用可变参数,所以实现起来很是费劲,我现在倒觉得可以使用类模板带可变参数实现。这样类模板实例化的类,虽然只支持注册和调用参数固定调用体,但具有编译期类型检查的好处,Register和UnRegister均很容易实现。用户需要时,根据需要实例化模板即可。
template <typename AbstractProduct ,typename IdentifierType ,typename... Arg > class Factory { public: bool Register(const IdentifierType& id, std::function<unique_ptr<AbstractProduct> (Arg...)> creator) { return associations_.insert(typename AssocMap::value_type(id,creator)).second; } bool UnRegister(const IdentifierType& id) { return associations_.erase(id)==1; } unique_ptr<AbstractProduct> Createobject(const IdentifierType& id,Arg&&... args) { typename AssocMap::const_iterator i =associations_.find(id); if(i != associations_.end()) { return (i->second)(std::forward<Arg>(args)...); } assert(false); } private: typedef std::unordered_map<IdentifierType,std::function<unique_ptr<AbstractProduct> (Arg...)> > AssocMap; AssocMap associations_; };
需要注意一下,这代码因为使用变长模板参数,所以无法使用默认模板参数。
使用第一种实现方式,你只需要一个对象,即可搞定所有需求(UnRegister比较闹心)。具有运行期类型检查。
使用第二种实现方式,你要在不同场合下,实例化出类,并且创建出对象只支持注册某种调用格式调用体。而且具有编译期类型检查。
虽然在第一种方式上,下了很多功夫,但是我还是不推荐这种做法,除非真的有万不得已需要,非要把代码设计成这样。
春节前,最后一篇文章,转载请标明出处,谢谢。