在實際專案中,常面臨需透過統一介面控制多種型號裝置,且須支援未來擴充新型號的需求。若在編譯期硬編碼裝置配置,系統將失去彈性。理想目標是能在執行期根據字串型號名稱,動態建立對應的裝置物件實例,例如:Motor* motor = ClassByName("IM9001");。
首先定義一個抽象基類 IntelligentMotor 作為協議介面,所有具體裝置類別(如 IM9001、IM9002)皆繼承此基類並實作純虛擬函式:
class IntelligentMotor {
public:
IntelligentMotor(const std::string& port_name);
virtual bool Start() = 0;
virtual bool Stop() = 0;
virtual ~IntelligentMotor();
};
class IM9001 : public IntelligentMotor {
public:
IM9001(const std::string& port_name);
virtual bool Start();
virtual bool Stop();
virtual ~IM9001();
private:
// 具體實作細節...
};
class IM9002 : public IntelligentMotor { /* ... */ };
// 更多型號...
最直覺的做法是在 ClassByName 中使用 if-else 鏈:
IntelligentMotor* ClassByName(const std::string& model_name, const std::string& port_name) {
if (model_name == "IM9001") return new IM9001(port_name);
else if (model_name == "IM9002") return new IM9002(port_name);
// 所有支援型號...
else throw "不支援該型號裝置。";
}
缺點: 型號增加時代碼膨脹,維護性差,違反開放封閉原則。
建立型號名稱到物件指標的映射表:
struct Map { const std::string ModelName; IntelligentMotor* Device; };
Map ModelTable[] = { {"IM9001", new IM9001}, {"IM9002", new IM9002} };
嚴重缺陷:
改為儲存函式指標,延遲建立實例:
template <class T>
IntelligentMotor* Creator(const std::string& port_name) { return new T(port_name); }
typedef IntelligentMotor* (*CreatorFunc)(const std::string&);
struct Map { const std::string ModelName; CreatorFunc DeviceCreator; };
Map model_table[] = {
{"IM9001", &Creator<IM9001>},
{"IM9002", &Creator<IM9002>}
};
IntelligentMotor* ClassByName(const std::string& model_name, const std::string& port_name) {
for (auto& entry : model_table) {
if (model_name == entry.ModelName) return entry.DeviceCreator(port_name);
}
throw "不支援該型號裝置。";
}
此法解決記憶體浪費與建構函式問題,且擴充新型號只需在表格加入一行。
若型號眾多,線性搜尋效率低,改用 std::map 實現 O(log N) 查找:
typedef std::map<std::string, CreatorFunc> LookupTable;
LookupTable lookup_table;
// 初始化時填入 model_table 內容...
IntelligentMotor* ClassByName(const std::string& model_name, const std::string& port_name) {
auto it = lookup_table.find(model_name);
if (it == lookup_table.end()) throw "不支援該型號裝置。";
return it->second(port_name);
}
此為「一次建立索引,多次攤銷成本」策略,視實際情況權衡。
將實作封裝為動態連結庫,僅輸出 ClassByName 介面與基類定義。新增型號時只需重新編譯對應模組,主程式無需重新編譯甚至重新連結,實現真正的模組化擴充。
本文示範了從簡單條件判斷演進至 Factory Pattern 的完整過程。更通用、高效的物件工廠實作請參考 Andrei Alexandrescu 《Modern C++ Design》第 8 章 Object Factory。