假设你在编写一个简单的绘图程序,该程序允许使用者编辑简单的向量图形(包括线、圆、多边形等)。采用面向对象风格,定义一个 abstract class shape,让所有图形都派生于它:
class Shape { public: virtual void Draw() const = 0; virtual void Rotate(double angle) = 0; virtual void Zoom(double zoomFactor) = 0; };
可以定义一个包含复杂图形的 Drawing class 用于保存着一个集合,其内都是 pointer to Shape,并提供某些操作,将复杂图形视为一个整体来处理。
两个典型的操作:(1)将图形保存为文件,(2)从先前保存的文件中取出图形
class Drawing { .... public: void Save(std::ofstream& outFile); void Load(std::ifstream& inFile); }; void Drawing::Save(std::ofstream& outFile) { write drawing header for (each element in the drawing) { (current element)->save(outFile); } }
当从文件装载图形时,我们必须知道正在装在图形的类型(线、圆...),一个简单的做法是,要求每一个从 Shape 派生的对象都在文件起始处存一个整数标识符。每个对象都一个自己独一无二的 ID。文件的去读将会像这样:
namespace DrawingType { const int LINE = 1, POLYGON = 2, CIRCLE = 3; }; void Drawing::Load(std::ifstream& inFile) { // error handling omitted for simplicity while (inFile) { // read object type int drawingType; inFile >> drawingType; // create a new empty object Shape* pCurrentObject; switch (drawingType) { case LINE: pCurrentObject = new Line; break; case POLYGON: pCurrentObject = new Polygon; break; ... } // read the object's contents by invoking a virtual fn pCurrentObject->Read(inFile); add the object to the container } }
它基于型别标记(type tag)执行了 switch 语句,因而带有 switch 语句的相应缺点。
它在一个源码文件中收集所有关于Shape 派生类的相关信息;对于所有可能的图形, Drawing::Save 的实现文件都必须包含其头文件,造成改变以依存和维护上的瓶颈。
它难以扩充。扩充时必须增加图形 class、图形的 type tag、图形相应的 switch 语句。
希望产生的 object factory:它能完成我们的工作,但不存在以上缺点。拿掉 switch 语句,使 Line 的生成语句可以放在 Line 的实作文件中。“工厂”维护着一个由函数指针组成的集合,所有函数指针所指向的函数都具有 Shape* CreateConcreteShape();这样的形式。此外 ID 和 “用于产生相应对象” 的函数指针之间必须对应。针对特定的型别标识符,map 可以提供相应的函数取用机会,这正是 switch 语句提供的功能。map 还具有可伸缩性,这是编译器便被固定住的
switch 语句无法提供弹性。map 可与执行期增长,可动态增加元素(ID 和 函数指针的组合)。
为什么不使用vector?
ID 是整数,所以我们应该可以维护一个 vector,让 ID 称为 vector 的索引。那的却更快速,但这里使用 map更好一些,因为 map 并不要求其索引是连续值,而且 map 更具有一般性-----
vector 只能使用整数索引, maps 却可以拿任何有序型别作为索引。在泛型化 Object Factory 时,这一点很重要。
class Shape { public: virtual void Draw() const = 0; virtual void Rotate(double angle) = 0; virtual void Zoom(double zoomFactor) = 0; }; class Line : public Shape { public: void Draw() const { } void Rotate(double angle) { } void Zoom(double zoomFactor) { } }; class ShapeFactory { public: typedef Shape* (*CreateShapeCallback)(); private: typedef std::map<int, CreateShapeCallback> CallbackMap; public: // Return true if registration was successful bool RegisterShape(int ShapId, CreateShapeCallback CreateFn); // Return true if the ShapeId was registered before bool UnregisterShape(int ShapId); Shape* CreateShape(int ShapId); private: CallbackMap callbacks_; }; bool ShapeFactory::RegisterShape(int ShapId, CreateShapeCallback CreateFn) { return callbacks_.insert(CallbackMap::value_type(ShapId, CreateFn)).second; } bool ShapeFactory::UnregisterShape(int ShapId) { return callbacks_.erase(ShapId) == 1; } Shape* ShapeFactory::CreateShape(int ShapId) { CallbackMap::const_iterator i = callbacks_.find(ShapId); if (i == callbacks_.end()) { // not found throw std::runtime_error("Unknown Shape ID"); } return (i->second)(); } ShapeFactory ShapeFactory_; namespace { Shape* CreateLine() { return new Line; } // The ID of class Line const int LINE = 1; // Assume TheShapeFactory is a singleton factory const bool registered = ShapeFactory_.RegisterShape(LINE, CreateLine); }