Object Factories(对象工厂)

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比较闹心)。具有运行期类型检查。

使用第二种实现方式,你要在不同场合下,实例化出类,并且创建出对象只支持注册某种调用格式调用体。而且具有编译期类型检查。

虽然在第一种方式上,下了很多功夫,但是我还是不推荐这种做法,除非真的有万不得已需要,非要把代码设计成这样。

春节前,最后一篇文章,转载请标明出处,谢谢。

时间: 2024-10-05 22:09:58

Object Factories(对象工厂)的相关文章

再谈 Object Factories(对象工厂)

为何而写: 为什么再谈一次,因为上次代码实在是不够好.上篇文章对象工厂给出的代码太过拙劣,限于学识,我自己类型擦除技术仅仅是把对象的指针转换为void* 而已,实际上可以更为巧妙.这次利用新的类型擦出技术,给出一个完美的解决方,请看下文. 前情描述: 我为什么不直接保存用户提供的function的void*而非要把他copy一份利用容器vector储存起来,然后再去取其指针转换为void*.是因为用户传来的function可能是右值或临时值,一个马上要被删除的值,如果我们直接使用用户提供的fun

对象工厂函数与构造函数

本文内容: 1.概述 2.使用工厂函数创建对象 3.定义对象“构造”函数 4.对象的constructor属性 5.以普通方式调用的对象“构造”函数 ★概述: 使用对象字面量,或者向空对象中动态地添加新成员,是最简单易用的对象创建方法.然而,除了这两种常用的对象创建方式,JavaScript还提供了其他方法创建对象. ★使用工厂函数创建对象 我们可以编写一个函数,此函数的功能就是创建对象,可以将其称为“对象工厂方法”. ★ 定义对象“构造”函数 注意点:对象构造函数首字母大写:内部使用this关

Javascript我学之六对象工厂函数与构造函数

本文是金旭亮老师网易云课堂的课程笔记,记录下来,以供备忘. 概述 使用对象字面量,或者向空对象中动态地添加新成员,是最简单易用的对象创建方法. 然而,除了这两种常用的对象创建方式,JavaScript还提供了其他方法创建对象. 1).使用工厂函数创建对象 我们可以编写一个函数,此函数的功能就是创建对象,可将其称为“对象工厂方法”. 1 //工厂函数 2 function createPerson(name, age, job) { 3 var o = new Object(); 4 o.name

MyBatis配置文件(五)--objectFactory对象工厂

我们在使用MyBatis执行查询语句的时候,通常都会有一个返回类型,这个是在mapper文件中给sql增加一个resultType(或resultMap)属性进行控制.resultType和resultMap都能控制返回类型,只要定义了这个配置就能自动返回我想要的结果,于是我就很纳闷这个自动过程的实现原理,想必大多数人刚开始的时候应该也有和我一样的困惑和好奇,那么今天我就把自己的研究分享一下.在JDBC中查询的结果会保存在一个结果集中,其实MyBatis也是这个原理,只不过MyBatis在创建结

Objective -C Object initialization 对象初始化

Objective -C Object initialization 对象初始化 1.1 Allocating Objects  分配对象 Allocation is the process by which a new object is born. allocation 是新对象诞生的过程. Sending the alloc message to a class causes that class to allocate a chunk of memory large enough to

[Twisted] Protocols协议和Protocol Factories 协议工厂

Protocols 描述了如何异步处理网络事件.Twisted维护了许多协议的实现,如HTTP,Telent,DNS,IMAP.Portocols实现了IProtocol接口, IProtocol包含如下方法: makeConnection:在两个节点中间创建连接.节点通过transport连接. connectionMade:当连接建立时调用. dataReceived:当数据到达时调用. connectionLost:当连接关闭时调用. Protocol Factories Factory用

Qt Meta Object System-元对象系统

Qt Meta Object System-元对象系统 元对象系统的构成 QObject为所有需要利用元对象系统的对象提供一个基类. Q_OBJECT宏,在类的声明体内激活meta-object功能,比如动态属性.信号和槽. Meta Object Compiler(MOC),为每个QObject派生类生成代码,以支持meta-object功能. QObject定义了从一个QObject对象访问meta-object功能的接口,Q_OBJECT宏用来告诉编译器该类需要激活meta-object功

C# 将object类型对象(注:必须是可序列话的对象)转换为二进制序列字符串

1 public string SerializeObject(object obj) 2 { 3 //将object类型对象(注:必须是可序列化的对象)转换为二进制序列字符串 4 IFormatter formatter=new BinaryFormatter(); 5 string result=string.Empty; 6 using(MemoryStream stream=new MenmoryStream()) 7 { 8 formatter.Serialize(sream.obj)

241 获取对象的属性名:Object.keys(对象)

Object.keys(对象) :获取到当前对象中的属性名 ,返回值是有元素为字符串的一个数组,效果类似 for-in. <script> // 用于获取对象自身所有的属性 var obj = { id: 1, pname: '小米', price: 1999, num: 2000 }; var arr = Object.keys(obj); console.log(arr); // ["id", "pname", "price",