代碼審查中同事建議:「使用了多個 instanceof,這是雙重分派的跡象,可考慮訪問者模式。」由此引發對 Double Dispatch 與 Visitor Pattern 的深度探索。
this 對象的動態類型決定執行邏輯(一次動態綁定)。Sales 和 Vehicle)共同決定邏輯。在 Sales 中提供 GetDiscountRate(Vehicle&) 和 GetDiscountRate(Benz&) 重載。但 重載解析發生在編譯期,參數靜態類型為 Vehicle& 時永遠調用前者,無法實現運行時雙重分派。
在 Vehicle 增加 GetManagerDiscountRate() 虛函數,Benz 重寫之。SalesManager::GetDiscountRate 調用該虛函數。實現了:
Sales/SalesManager 的虛函數區分銷售角色Vehicle/Benz 的虛函數區分車型雖可行,但擴展性差:新增車型需修改所有銷售類,新增銷售角色需修改所有車類。
利用 編譯期知道 *this 類型 的特性,反向調用:
// 銷售基類
class ISales {
public:
virtual double Accept(IVehicle& v) = 0;
};
class SalesManager : public ISales {
double Accept(IVehicle& v) override { return v.Visit(*this); } // *this 編譯期類型為 SalesManager
};
// 車輛接口
class IVehicle {
public:
virtual double Visit(Sales&) = 0;
virtual double Visit(SalesManager&) = 0;
};
class Benz : public IVehicle {
double Visit(Sales&) override { return 0.06; }
double Visit(SalesManager&) override { return 0.066; }
};
| 操作 | 難度 | 說明 |
|---|---|---|
| 新增車型 (如 Fiat) | 容易 | 繼承 IVehicle 實現兩個 Visit 即可,無需修改現有代碼 |
| 新增銷售角色 | 困難 | 需修改 IVehicle 和所有車型類,添加新的 Visit 重載 |
此為「表達式問題」體現。進階模式如 Acyclic Visitor 可雙向擴展,但複雜度較高。
C++ 無原生雙重分派支持,因語言設計權衡。訪問者模式是 C++ 中模擬雙重分派的標準手法,通過「雙重虛函數調用」將運行時類型信息傳遞給編譯期重載解析。理解其本質有助於在面對多維度多態決策時做出正確架構選擇。