C++自注册工厂
假设我们现在需要实现一个绘制类,不同的图形有不同的绘制方式,我们可以大致写出以下代码:
1.普通的工厂模式,大家想必非常熟悉,伪代码如下:
class Sharp
{
public:
virtual ~Sharp()
{
}
virtual void paint() = 0;
};
//圆形
class Circle : public Sharp
{
public:
virtual void paint() override
{
qDebug() << "paint Circle";
}
};
//矩形
class Rectangle : public Sharp
{
public:
virtual void paint() override
{
qDebug() << "paint Rectangle";
}
};
class SharpFactory
{
public:
static SharpFactory* getInstance()
{
static SharpFactory ins;
return &ins;
}
void registerSharp(int type, Sharp* ins)
{
if (!m_sharpMap.contains(type))
{
m_sharpMap.insert(type, ins);
}
}
template<class T = Sharp>
T* getSharp(int type)
{
Sharp* ptr = m_sharpMap.value(type, nullptr);
if (ptr != nullptr)
{
return dynamic_cast<T*>(ptr);
}
return nullptr;
}
private:
QHash<int, Sharp*> m_sharpMap;
};
这是一个最简单的工厂模式,我们要如何使用这个工厂模式呢? 如下:
//1.先创建图形类对象
Sharp* clrcle = new Circle();
Sharp* rect = new Rectangle();
// Sharp* customSharp = new customSharp();
//2.将类对象注册到工厂中去
SharpFactory::getInstance()->registerSharp(0, clrcle);
SharpFactory::getInstance()->registerSharp(1, rect);
//3.使用时,根据类型到工厂中取具体的对象
Sharp* ins = SharpFactory::getInstance()->getSharp(0);
if (ins != nullptr)
{
ins->paint();
}
以上就是这个工厂模式的使用方式,我们先思考这个工厂有什么特点:
- 新增一种图形时,只需要新增一个类,继承自Sharp类,并重新实现
virtual void paint() override;
函数即可,无需对现有代码做任何改动 - 新增一种图形
(如椭圆,ellipse)
时,需要在程序中的某处,手动Sharp* ell = new ellipse();
,并需要调用SharpFactory::getInstance()->registerSharp(2, ell);
将其注册到工厂中去; 如果我忘记了写这两行代码,或者我仅仅只构造了椭圆对象,但是忘了将其注册到工厂中去,那么在我想获取椭圆对象时,就无法通过工厂类的Sharp* ell = SharpFactory::getInstance()->getSharp(2);
获取到椭圆类对象 - 我在程序中某处手动
new
出来的这些Sharp
类对象,需要在何处进行释放?
1.遵循谁创建就由谁释放的原则;由程序员取手动管理
2.让工厂类取统一管理,在工厂类的析构函数中全部释放,譬如:
~SharpFactory()
{
for (QHash<int, Sharp*>::const_iterator iter = m_sharpMap.begin(); iter != m_sharpMap.end(); ++iter)
{
Sharp* item = iter.value();
if (item != nullptr)
{
delete item;
item = nullptr;
}
}
}
在这里,我个人认为使用第二种方式,将这些对象的生命周期全部交给工厂类去管理,会更合适
这里一定会有同学说,既然是我创建的对象,那么应该由我释放,为什么要交给工厂类去释放呢?
针对这个问题,我们思考一下,如果以上代码中的Shape
类和SharpFactory
工厂类是由框架的开发者
写的,而具体的图形类,是由应用程序的开发者
去写,框架的开发者更希望应用程序的开发者只关心自己的业务逻辑,而不需要去关心其他的因素,其他的处理框架可以帮应用程序的开发者处理好,这时候,我们希望应用程序的开发者将自己继承Sharp类写的具体图形类对象,注册到工厂中去,而又可以将new
的细节隐藏,这样的话,对于应用程序开发者来说,他没有去手动new
这个对象,自然他不会去释放这个对象
那我们如何将new
的细节隐藏起来,不需要应用程序开发者手动创建呢?
其实我们可以通过模板类,参考一下代码:
template<class CustomSharp>
class SharpRegister
{
public:
SharpRegister(int type)
{
// std::function<Sharp* ()> func = []() -> Sharp* { return new CustomSharp;};
SharpFactory::getInstance()->registerSharp(type, []() -> Sharp* { return new CustomSharp;}());
}
};
我们使用一个模板类,这个类的构造函数中调用 new
操作符来创建一个Sharp
类型的指针,并注册到工厂类中,在使用时候,我们仅需要创建SharpRegister
类的对象即可,比如我们可以使用一下代码来代替上面的创建类对象:
SharpRegister<Circle> circleSharpRegister(0);
SharpRegister<Rectangle> rectSharpRegister(1);
当然,这两个模板类对象(circleSharpRegister, rectSharpRegister)
,在我们的程序中,除了将Circle
和Rectangle
这两个类对象注册到工厂中,就没有任何其它作用,所以我们可以更一步简化注册的流程,我们甚至可以用宏定义来实现注册,例如:
#define CREATE_SHARP_NAME(T) reg_sharp_##T##_ //生成变量名
#define REGISTER_SHARP(type, T) static SharpRegister<T> CREATE_SHARP_NAME(T)(type);
REGISTER_SHARP宏 是用来创建一个静态的 SharpRegister模板类对象,我们为了让这些静态类对象的对象名不重复,所以才有 CREATE_SHARP_NAME宏,用来生成对象名
这样改造之后,如果新增一个椭圆类ellipse
,应用程序的开发者仅需要在ellipse
类的实现文件中写上一行:
REGISTER_SHARP(3, ellipse);
即可,就能实现将ellipse
类对象注册到工厂中
提一嘴
当前这个工厂中,存储的是Sharp
类的子类对象的指针,每次根据type
获取时,拿到的都是同一个对象,如果我希望每次根据type
都能拿到一个全新的Sharp
子类对象,那么就不能直接将创建好的类对象指针
放入到容器中;
而是需要将创建该类对象的函数(实例化器)
放到注册到工厂中,然后通过工厂类的get
函数,获取到这个实例化器
,再调用该实例化器函数即可
//工厂类容器修改
QHash<int, std::function<Sharp* ()>> m_sharpMap;
//获取一个全新的类对象
Sharp* getSharp(int type)
{
std::function<Sharp* ()> func = m_sharpMap.value(type, nullptr);
if (func != nullptr)
{
return func();
}
return nullptr;
}
//注册模板类
template<class CustomSharp>
class SharpRegister
{
public:
SharpRegister(int type)
{
std::function<Sharp* ()> func = []() -> Sharp* { return new CustomSharp;};
SharpFactory::getInstance()->registerSharp(type, func);
}
};
思考:
经过以上的改造,我们这个工厂可以通过宏定义来实现自动注册,看似已经非常完美,但是这里其实还有几个缺点
- 使用
REGISTER_SHARP
会创建静态对象,静态对象的初始在在main函数之前,这样也许会增加程序启动的时长 - 如果我希望仅有一个Sharp类,有很多种画法(函数),而不是希望扩展画法时候,是通过新增类来实现,比如:
class Sharp
{
public:
virtual ~Sharp()
{
}
virtual void paintCircle()
{
qDebug() << "paint clrcle";
}
virtual void paintRect()
{
qDebug() << "paint rect";
}
virtual void paintEllipse()
{
}
};
这种时候我们需要将工厂稍微修改一下,工厂类的容器中存储的就不再是Sharp*
类型,而应该是函数指针类型,指向绘制函数
,我们稍微将这个工厂类和注册类改造一下:
#define CREATE_SHARP_NAME(T) reg_sharp_##T##_ //生成变量名
#define REGISTER_SHARP(type, func) static SharpRegister<T> CREATE_SHARP_NAME(type) (type, func);
using PaintFunc = std::function<void ()>;
class SharpFactory
{
public:
static SharpFactory* getInstance()
{
static ClassFactory ins;
return &ins;
}
void registerPaintFunc(int type, PaintFunc paintFunc)
{
if (!m_sharpMap.contains(type))
{
m_sharpMap.insert(type, paintFunc);
}
}
PaintFunc getPaintFunc(int type)
{
return m_sharpMap.value(type, nullptr);
}
private:
QHash<int, PaintFunc> m_sharpMap;
};
class SharpRegister
{
public:
SharpRegister(int typ, PaintFunc func)
{
SharpFactory::getInstance()->registerPaintFunc(type, func);
}
};
可以看到,注册类不再是模板类;在使用处,需要构造Sharp
或其子类的对象 m_sharp
,并且需要这个宏,将类对象的函数指针注册到工厂中去,并且m_sharp
对象的名称周期应该由构造者进行管理
REGISTER_SHARP(0, std::bind(&Sharp::paintCircle, m_sharp));
REGISTER_SHARP(1, std::bind(&Sharp::paintRect, m_sharp));
REGISTER_SHARP(2, std::bind(&Sharp::paintEllipse, m_sharp));