當系統中存在某抽象基類中有不少具體子類,一個簡單實用的策略是建立對象的邏輯封裝到一個工廠方法中。這樣,能夠在不影響客戶端代碼的狀況下擴展具體子類。可是一個低質量的實現(好比像下面的代碼,使用了 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 語句說再見了。鑑於自身水平有限,文章中有不免有些錯誤,歡迎你們指出。也但願你們能夠積極留言,與筆者一塊兒討論編程的那些事。指針