一、抽象基类
抽象基类是包含一个或多个纯虚成员函数的类。它不是实体类,不能使用new操作符进行实例化,而只能用作一个基类,并由派生类提供纯虚方法的实现。e.g.
//renderer.h
class IRenderer
{
public:
virtual ~IRenderer() {}
virtual void Render() = 0;
virtual void SetViewportSize(const std::string& filename) = 0;
};
严格讲,“纯虚方法不提供实现”的说法是不正确的。实际上,可以再.cpp文件中为纯虚方法提供默认实现。例如,可以再render.cpp中为SetViewportSize()提供一个实现,然后派生类就可以调用IRenderer::SetViewportSize(),但它仍需要显示地重写此方法。
抽象基类可用于描述多个类共享的行为的抽象单元,它指定了所有派生类都必须遵循的契约。在Java中,它被称为接口(Java接口只能包含公有方法、静态变量且不能定义构造函数)。将上述的类命名为IRender,以I作为前缀,以此表明它是一个接口类。
二、工厂示例
//rendererfactory.h
#include "renderer.h"
#include <string>
class RendererFactory
{
public:
IRenderer* CreateRenderer(const std::string& type);
};
//rendererfactory.cpp
#include "rendererfactory.h"
#include "openglrenderer.h"
#include "directxrenderer.h"
#include "mesarenderer.h"
IRenderer* RendererFactory::CreateRenderer(const std::string& type)
{
if (type == "opengl")
return new OpenGLRenderer();
if (type == "directx")
return new DirectXRenderer();
if (type == "mesa")
return new MesaRenderer();
return NULL;
}
此工厂方法能够返回IRenderer的3个派生类中的任意一个,返回那个派生类则取决于客户传递的类型字符串。这就允许用户在运行时决定要创建哪个派生类,而不是在编译时要求用户使用正常的构造函数创建类。这是一个巨大的优势,因为它意味着能够基于用户输入或根据运行时读入的配置文件创造不同的类。
另外,还要注意各个具体的派生类的头文件仅包含在工厂的.cpp文件中。它们不会出现在公有头文件rendererfactory.h中。实际上,这些都是私有头文件,且不需要和API一起发布。因此,用户永远看不到不同渲染器的私有细节,他们甚至看不到实现不同渲染器的具体类型。用户只能通过字符串变量指定渲染器(也可以使用枚举类型)。
这是一个完全可行的工厂方法。但它的一个潜在缺陷是,包含了可用的派生类的硬编码信息。如果要为系统添加新的渲染器,则必须修改rendererfactory.cpp。这个改动不是非常繁重,而且最重要的是它不会影响公有的API。但这也意味着不能为新的派生类添加运行时支持。换句话说,用户不能为系统添加新的渲染器。这些问题可以使用可扩展的对象工厂来解决。
三、扩展工厂示例
//rendererfactory.h
#include "renderer.h"
#include <string>
#include <map>
class RendererFactory
{
public:
typedef IRenderer* (*CreateCallback)();
static void RegisterRenderer(const std::string& type, CreateCallback cb);
static void UnregisterRenderer(const std::string& type);
static IRenderer* CreateRenderer(const std::string& type);
private:
typedef std::map<std::string, CreateCallback> CallbackMap;
static CallbackMap mRenderers;
};
//rendererfactory.cpp
#include "rendererfactory.h"
//在RendererFactory中实例化静态变量
RendererFactory::CallbackMap RendererFactory::mRenderers;
void RendererFactory::RegisterRenderer(const std::string& type, CreateCallback cb)
{
mRenderers[type] = cb;
}
void RendererFactory::UnregisterRenderer(const std::string& type)
{
mRenderers.erase(type);
}
IRenderer* RendererFactory::CreateRenderer(const std::string& type)
{
CallbackMap::iterator it = mRenderers.find(type);
if (it != mRenderers.end())
{
//调用回调以构造此派生类型的对象
return (it->second)();
}
return NULL;
}
//main.cpp
#include "rendererfactory.h"
class UserRenderer : public IRenderer
{
public:
~UserRenderer() {}
void SetViewportSize(int w, int h) {}
void Render() { std::cout << "User Render" << std::endl; }
static IRenderer* Create() { return new UserRenderer(); }
};
int main()
{
//注册一个新的渲染器
RendererFactory::RegisterRenderer("user", UserRenderer::Create);
//为新渲染器创建一个实例
IRenderer* r = RendererFactory::CreateRenderer("user");
r->Render();
delete r;
return 0;
}
工厂类维护一个映射,此映射将类型名与创建对象的回调关联起来。然后就可以允许新的派生类通过一对新的方法调用来实现注册和注销。
需要注意的另一个问题是,工厂对象必须保存其状态信息。因此,最好强制要求任一时刻都只能创建一个工厂对象。这也是为何多数工厂对象也是单例的原因。
参考资料:
《C++ API设计》