当系统中存在某抽象基类中有不少具体子类,一个简单实用的策略是建立对象的逻辑封装到一个工厂方法中。这样,能够在不影响客户端代码的状况下扩展具体子类。可是一个低质量的实现(好比像下面的代码,使用了 switch 语句),会致使编译的高耦合以及扩展的高成本,经过阅读 《modern c++ design》一书,看到了一个比较优雅的解决方法。c++
如今假设咱们要实现一个图形管理系统,其中 Shape 是抽象基类,声明以下:编程
class Shape { public: virtual void Save(std::ofstream &out_file) = 0; virtual void Read(std::ifstream &in_file) = 0; virtual ~Shape() { } };
Shape::Save() 接口将图形存储到本地文件中(其实该接口是一个很差的设计,其参数应该是一个可写入对象便可,无需是一个ofstream)。Shape::Read() 接口从文件中恢复出图形中的全部信息。存储图形的策略是在文件头部存入一个 int ,表明图形的类型,ShapeFactory 负责经过这个 type 来建立适当的 Shape。Drawing 类负责将一个 Shape 对象存储到本地文件或者从文件中恢复出来。其声明以下:函数
#include "shape.h" class Drawing { public: Drawing(Shape *p) : p_shape_(p) { } void Save(std::ofstream &out_file); Shape *Load(std::ifstream &in_file); private: Shape *p_shape_; };
一个直观的 ShapeFactory 实现可能以下:
#include "shape_types.h" class ShapeFactory { public: Shape *CreateShape(int type) { switch (tyep) { case line_type: return new Line(); case circle_type: return new Circle(); default: throw std::runtime_error("Unknown type"); } } };
各类表明子类的 type 定义于 shape_types.h 头文件中。可是这样的实现引入了 switch 语句,其让系统的扩充变得举步维艰。试想咱们如今想为系统中加入一个新的子类 Rectangle,咱们须要作什么?
这样的扩展成本显然是难以让众多挑剔的程序猿(媛)满意的,可是最大的问题是其违反了程序设计原则(开闭原则),是的,如今是时候向代码中万恶的 switch 宣战了。函数指针能够成为咱们的得力臂助,经过引入一个从 type 到函数指针的索引,咱们能够消除 switch 语句,这个索引在这里咱们选择了 map,在这个例子中可能有人会以为 vector 是个更好的选择,可是我以为 vector 须要连续的下标,而且在查找速度上有问题(虽然一个系统不太可能存在数量多到没法忽视的子类)。让咱们来看增强版的测试
ShapeFactory: class ShapeFactory { public: typedef Shape *(*CreateFn)(); private: typedef std::map<int, CreateFn> CreateFnMap; public: bool RegisterShape(int shape_id, CreateFn); bool UnregisterShape(int shape_id); Shape *CreateShape(int shape_id) const; private: CreateFnMap fn_map_; };
经过 RegisterShape() 和 UnregisterShape() 实现动态添加/删除系统中支持的子类。而最终,每一个具体子类的建立逻辑都放在了单独的 CreateFn 中。其多是相似下面的简单代码:
Shape *CreatLine() { return new Line(); }
也能够是包含大量复杂逻辑的建立函数(固然,这里能够经过把 CreateFn 的类型改成 std::function 提供更多的扩展性)。ShapeFactory 的具体实现比较直白:
#include "shape.h" bool ShapeFactory::RegisterShape(int shape_id, CreateFn fn) { return fn_map_.insert(std::make_pair(shape_id, fn)).second; } bool ShapeFactory::UnregisterShape(int shape_id) { return fn_map_.erase(shape_id) == 1; } Shape *ShapeFactory::CreateShape(int shape_id) const { auto it = fn_map_.find(shape_id); if (it == fn_map_.end()) { throw std::runtime_error("Unknown Shape ID"); } return (it->second)(); }
如今每一个 class 之间作到了隔绝,每一个图形的 type 能够不须要保存在一个公共的头文件中,为了防止不一样的图形类型的 type 重复,致使 Register 失败,咱们还体贴的为 RegisterShape 返回一个 bool 值,在 Register 失败的时候会返回 false 来通知调用者。咱们将全部的职责从某个集中点(switch语句)转义到了每一个具体类中,它要求为每一个类别对工厂进行注册。若是要定义新的 Shape 派生类,咱们如今只须要“增长”新文件,而没必要“修改”旧文件。spa
附上测试代码:设计
#include "shape.h" #include "circle.h" #include "line.h" #include "drawing.h" #include <fstream> ShapeFactory g_factory; Shape *CreateLine() { return new Line(); } Shape *CreateCircle() { return new Circle(); } template<class S> void Test(S shape) { using namespace std; ofstream f("tmp"); S s; Drawing dr(&s); dr.Save(f); f.close(); ifstream f2("tmp"); Shape *p = dr.Load(f2); delete p; } int main() { g_factory.RegisterShape(line_type, CreateLine); g_factory.RegisterShape(circle_type, CreateCircle); Test(Line()); Test(Circle()); g_factory.UnregisterShape(line_type); Test(Line()); }
输出:
Line::Read() Circle::Read() libc++abi.dylib: terminating with uncaught exception of type std::runtime_error: Unknown Shape ID [1] 22666 abort ./a.out
运行结果与预期彻底一致,至此,咱们能够在工厂函数中对 switch 语句说再见了。鉴于自身水平有限,文章中有不免有些错误,欢迎你们指出。也但愿你们能够积极留言,与笔者一块儿讨论编程的那些事。指针