Factory Methods 工 厂 方 法 eryar@163.com 摘 要 Abstract: 本 文 主 要 是 对 API Design for C++ 中 Factory Methods 章 节 的 翻 译, 若 有 不 当 之 处, 欢 迎 指 正 关 键 字 Key Words:C++ Factory Pattern 一 概 述 Overview 工 厂 方 法 是 创 建 型 模 式, 允 许 在 不 指 定 需 要 创 建 对 象 类 型 的 情 况 下 创 建 出 对 象 本 质 上 来 说, 工 厂 方 法 就 是 一 个 通 用 的 构 造 函 数 C++ 中 的 构 造 函 数 有 以 下 几 种 限 制 : 无 返 回 值 (No return result) 在 构 造 函 数 中 不 能 返 回 一 个 值 这 就 意 味 着 : 例 如 当 构 造 失 败 时 不 能 返 回 一 个 NULL 作 为 初 始 化 失 败 的 信 号 命 名 有 约 束 (Constrained naming) 构 造 函 数 还 是 很 好 识 别 的, 因 为 它 的 命 名 必 须 与 类 名 一 样 静 态 绑 定 创 建 的 (Statically bound creation) 当 创 建 一 个 对 象 时, 必 须 指 定 一 个 在 编 译 时 就 能 确 定 的 类 名 如 :Foo *f = new Foo(),Foo 就 是 编 译 器 必 须 知 道 的 类 名 C++ 的 构 造 函 数 没 有 运 行 时 的 动 态 绑 定 功 能 (dynamic binding at run time) 无 虚 构 造 函 数 (No virtual constructors) 在 C++ 中 不 能 声 明 虚 的 构 造 函 数, 必 须 指 定 在 编 译 时 能 确 定 的 类 型 编 译 器 据 此 为 指 定 的 类 型 分 配 内 存, 然 后 调 用 基 类 的 默 认 构 造 函 数, 再 调 用 指 定 类 的 构 造 函 数 这 就 是 不 能 定 义 构 造 函 数 为 虚 函 数 的 原 因 相 反 地, 工 厂 方 法 (factory methods) 突 破 了 以 上 所 有 的 限 制 工 厂 方 法 的 基 本 功 能 就 是 一 个 可 以 返 回 一 个 类 的 实 例 的 简 单 函 数 但 是, 它 通 常 与 继 承 组 合 使 用, 派 生 的 类 可 以 重 载 工 厂 方 法 以 返 回 派 生 类 的 实 例 使 用 抽 象 基 类 (Abstract Base Classes) 来 实 现 工 厂 很 常 见, 也 很 有 用 二 抽 象 基 类 Abstract Base Classes 抽 象 基 类 就 是 包 含 一 个 或 多 个 纯 虚 函 数 (pure virtual methods) 的 类, 这 样 的 类 不 是 具 体 类 且 不 能 用 new 来 实 例 化 相 反 地, 它 是 作 为 其 它 派 生 类 的 基 类, 由 派 生 类 来 具 体 实 现 那 些 纯 虚 函 数 例 如 : #ifndef RENDERER_H #define RENDERER_H #include <string> An abstract interface for a 3D renderer. class IRenderer
virtual ~IRenderer() virtual bool LoadScene(const std:: ::string &filename) = 0; virtual void SetViewportSize(int w, int h) = 0; virtual void SetCameraPos(double x, double y, double z) = 0; virtual void SetLookAt(double x, double y, double z) = 0; virtual void Render() = 0; #endif 上 述 代 码 定 义 了 一 个 抽 象 基 类, 描 述 了 一 个 相 当 简 单 的 3D 图 形 渲 染 器 (renderer) 函 数 的 后 缀 =0 声 明 这 个 函 数 是 纯 虚 函 数, 表 示 这 个 函 数 必 须 由 其 派 生 类 来 具 体 实 现 抽 象 基 类 是 描 述 了 多 个 类 共 有 的 行 为 的 抽 象 单 元, 它 约 定 了 所 有 具 体 派 生 类 必 须 遵 守 的 合 同 在 Java 中, 抽 象 基 类 也 叫 接 口 (interface), 只 是 Java 的 接 口 只 能 是 公 用 的 方 法 (public method), 静 态 变 量, 并 且 不 能 定 义 构 造 函 数 将 类 名 IRenderer 带 上 I 就 是 为 了 表 明 这 个 类 是 接 口 类 (interface class) 当 然, 抽 象 基 类 中 并 不 是 所 有 的 方 法 都 必 须 是 纯 虚 函 数, 也 可 以 实 现 一 些 函 数 当 任 意 一 个 类 有 一 个 或 多 个 虚 函 数 时, 通 常 会 把 抽 象 基 类 的 析 构 函 数 声 明 为 虚 函 数 如 下 代 码 说 明 了 这 样 做 的 重 要 性 : class IRenderer // no virtual destructor declared virtual void Render() = 0; class RayTracer : public IRenderer RayTracer(); ~RayTracer(); void Render(); // provide implementation for ABC method int main(int, char **) IRenderer *r = new RayTracer(); // delete calls IRenderer::~IRenderer, not RayTracer::~RayTracer delete r; 三 简 单 工 厂 模 式 Simple Factory Example 在 复 习 了 抽 象 基 类 后, 让 我 们 在 简 单 工 厂 方 法 中 使 用 它 继 续 以 renderer.h 为 例, 声 明 创 建 工 厂, 创 建 的 对 象 类 型 为 IRenderer, 代 码 如 下 所 示 : #ifndef RENDERERFACTORY_H
#define RENDERERFACTORY_H #include "renderer.h" #include <string> A factory object that creates instances of different 3D renderers. class RendererFactory Create a new instance of a named 3D renderer. type can be one of "opengl", "directx", or "mesa" IRenderer *CreateRenderer(const std:: ::string &type); #endif 这 里 只 声 明 了 一 个 工 厂 方 法 : 它 只 是 一 个 普 通 的 函 数, 返 回 值 是 对 象 的 实 例 注 意 到 这 个 方 法 不 能 返 回 一 个 指 定 类 型 的 IRender 实 例, 因 为 抽 象 基 类 是 不 能 被 实 例 化 的 但 是 它 可 以 返 回 派 生 类 的 实 例 当 然, 你 可 以 使 用 字 符 串 作 为 参 数 来 指 定 需 要 创 建 对 象 的 类 型 假 设 已 经 实 现 了 派 生 自 IRender 的 三 个 具 体 类 : IRenderer::OpenGLRenderer, DirectXRenderer MesaRenderer 再 假 设 你 不 想 让 使 用 API 的 用 户 知 道 可 以 创 建 哪 些 类 型 : 他 们 必 须 完 全 隐 藏 在 API 后 面 基 于 这 些 条 件, 可 以 实 现 工 厂 方 法 的 程 序 如 下 : // 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 的 三 个 派 生 类 之 一 的 实 例, 取 决 于 传 入 的 参 数 字 符 串
这 就 可 以 让 用 户 决 定 在 运 行 时 而 不 是 在 编 译 时 创 建 哪 个 派 生 类, 这 与 普 通 的 构 造 函 数 要 求 一 致 这 样 做 是 有 很 多 好 处 的, 因 为 它 可 以 根 据 用 户 输 入 或 根 据 运 行 时 读 入 的 配 置 文 件 内 容 来 创 建 不 同 的 对 象 另 外, 注 意 到 实 现 具 体 派 生 类 的 头 文 件 只 在 rendererfactory.cpp cpp 中 被 包 含 它 们 不 出 现 在 rendererfactory.h 这 个 公 开 的 头 文 件 中 实 际 上, 这 些 头 文 件 是 私 有 的 头 文 件, 且 不 需 要 与 API 一 起 发 布 的 这 样 用 户 就 看 不 到 不 同 的 渲 染 器 的 私 有 细 节, 也 看 不 到 具 体 可 以 创 建 哪 些 不 同 的 渲 染 器 用 户 只 需 要 通 过 字 符 串 变 量 来 指 定 种 要 创 建 的 渲 染 器 ( 若 你 愿 意, 也 可 用 一 个 枚 举 来 区 分 类 型 ) 此 例 演 示 了 一 个 完 全 可 接 受 的 工 厂 方 法 但 是, 其 潜 在 的 缺 点 就 是 包 含 了 对 可 用 的 各 派 生 类 的 硬 编 码 若 系 统 需 要 添 加 一 个 新 的 渲 染 器, 你 必 须 再 编 辑 rendererfactory.cpp 这 并 不 会 让 人 很 烦, 重 要 的 是 不 会 影 响 你 提 供 的 公 用 的 API 但 是, 他 的 确 不 能 在 运 行 时 添 加 支 持 的 新 的 派 生 类 再 专 业 点, 这 意 味 着 你 的 用 户 不 能 向 系 统 中 添 加 新 的 渲 染 器 通 过 扩 展 的 对 象 工 厂 来 解 决 这 些 问 题 四 扩 展 工 厂 模 式 Extensible Factory Example 为 了 让 工 派 生 类 从 工 厂 方 法 中 解 耦, 且 允 许 在 运 行 时 添 加 新 的 派 生 类, 可 以 去 维 护 包 含 类 型 及 与 类 型 创 建 关 联 的 函 数 的 映 射 (map) 来 更 新 一 下 工 厂 类 可 以 通 过 添 加 几 个 新 的 函 数 用 来 注 册 与 注 销 新 的 派 生 类 在 运 行 时 能 注 册 新 的 类 允 许 这 种 类 型 的 工 厂 方 法 模 式 可 用 于 创 建 可 扩 展 的 接 口 还 有 个 需 要 注 意 的 事 是 工 厂 对 象 必 须 保 存 状 态, 即 最 好 只 有 一 个 工 厂 对 象 这 也 是 工 厂 对 象 通 常 是 单 件 的 (singletons) 为 了 程 序 的 简 单 明 了, 这 里 使 用 静 态 变 量 为 例 将 所 有 要 点 都 考 虑 进 来, 新 的 工 厂 对 象 代 码 如 下 所 示 : #ifndef RENDERERFACTORY_H #define RENDERERFACTORY_H #include "renderer.h" #include <string> #include <map> A factory object that creates instances of different 3D renderers. New renderers can be dynamically added and removed from the factory object. class RendererFactory The type for the callback that creates an IRenderer instance typedef IRenderer *(*CreateCallback)(); Add a new 3D renderer to the system static void RegisterRenderer(const std::string &type, CreateCallback cb); Remove an existing 3D renderer from the system static void UnregisterRenderer(const std::string &type);
Create an instance of a named 3D renderer static IRenderer *CreateRenderer(const std::string &type); private: typedef std::map<std::string, CreateCallback> CallbackMap; static CallbackMap mrenderers; #endif 为 了 程 序 的 完 整 性, 将 其.cpp 文 件 中 的 代 码 示 例 如 下 : #include "rendererfactory.h" #include <iostream> // instantiate the static variable in 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()) // call the creation callback to construct this derived type return (it->second)(); return NULL; 使 用 工 厂 对 象 创 建 派 生 类 的 方 法 如 下 所 示 : #include "rendererfactory.h" #include <iostream> using std::cout;
using std::endl; An OpenGL-based 3D renderer class OpenGLRenderer : public IRenderer ~OpenGLRenderer() bool LoadScene(const std::string &filename) return true; void SetViewportSize(int w, int h) void SetCameraPos(double x, double y, double z) void SetLookAt(double x, double y, double z) void Render() cout << "OpenGL Render" << endl; static IRenderer *Create() return new OpenGLRenderer; A DirectX-based 3D renderer class DirectXRenderer : public IRenderer bool LoadScene(const std::string &filename) return true; void SetViewportSize(int w, int h) void SetCameraPos(double x, double y, double z) void SetLookAt(double x, double y, double z) void Render() cout << "DirectX Render" << endl; static IRenderer *Create() return new DirectXRenderer; A Mesa-based software 3D renderer class MesaRenderer : public IRenderer bool LoadScene(const std::string &filename) return true; void SetViewportSize(int w, int h) void SetCameraPos(double x, double y, double z) void SetLookAt(double x, double y, double z) void Render() cout << "Mesa Render" << endl; static IRenderer *Create() return new MesaRenderer; int main(int, char **) // register the various 3D renderers with the factory object RendererFactory::RegisterRenderer("opengl", OpenGLRenderer::Create);
RendererFactory::RegisterRenderer("directx", DirectXRenderer::Create); RendererFactory::RegisterRenderer("mesa", MesaRenderer::Create); // create an OpenGL renderer IRenderer *ogl = RendererFactory::CreateRenderer("opengl"); ogl->render(); delete ogl; // create a Mesa software renderer IRenderer *mesa = RendererFactory::CreateRenderer("mesa"); mesa->render(); delete mesa; // unregister the Mesa renderer RendererFactory::UnregisterRenderer("mesa"); mesa = RendererFactory::CreateRenderer("mesa"); if (! mesa) cout << "Mesa renderer unregistered" << endl; return 0; 你 的 API 的 用 户 可 以 在 系 统 中 注 册 与 注 销 一 个 新 的 渲 染 器 编 译 器 将 会 确 保 用 户 定 义 的 新 的 渲 染 器 必 须 实 现 抽 象 基 类 IRenderer 的 所 有 抽 象 接 口, 即 新 的 渲 染 器 类 必 须 实 现 抽 象 基 类 IRenderer 所 有 的 纯 虚 函 数 如 下 代 码 演 示 了 用 户 如 何 自 定 义 新 的 渲 染 器, 在 工 厂 对 象 中 注 册, 并 叫 工 厂 对 象 为 之 创 建 一 个 实 例 : 这 里 需 要 注 意 的 一 点 是 我 向 类 UserRenderer 中 添 加 了 一 个 Create() 函 数, 这 是 因 为 工 厂 对 象 的 注 册 方 法 需 要 返 回 一 个 对 象 的 回 调 函 数 这 个 回 调 函 数 不 一 定 必 须 是 抽 象 基 类 IRenderer 的 一 部 分, 它 可 以 是 一 个 自 由 的 函 数 但 是 向 抽 象 基 类 IRenderer 中 添 加 这 个 函 数 是 一 个 好 习 惯, 这 样 就 确 保 了 所 有 相 关 功 能 的 一 致 性 实 际 上, 为 了 强 调 这 种 约 定, 可 以 将 Create 作 为 抽 象 基 类 IRenderer 的 一 个 纯 虚 函 数 五 结 论 Conclusion Finally, I note that in the extensible factory example given here, a renderer callback has to be visible to the RegisterRenderer() function at run time. However, this doesn t mean that you have to expose the built-in in renderers of your API. These can still be hidden either by registering them within your API initialization routine or by using a hybrid of the simple factory and the extensible factory, whereby the factory method first checks the type string against a few built-in in names. If none of those match, it then checks for any names that have been registered by the user. This hybrid approach has the potentially desirable behavior that users cannot override your built-in in classes.