在上一篇文章中,我們探討了面向對象的核心概念之一——封裝。雖然封裝相對直觀易懂,但在實際開發中,僅靠封裝往往無法妥善解決複雜問題。讓我們透過一個具體的遊戲戰鬥系統例子,深入理解繼承與多態如何優雅地解決這類挑戰。
假設我們要設計一個對戰遊戲的戰鬥細節,初期版本僅支援一種動作——fight。遊戲中有三種角色:fighter(戰士)、knight(騎士)和 warrior(武士),每種角色擁有不同的健康值、攻擊力、名稱和戰鬥速度。基於封裝思想,我們自然會為每個角色建立獨立的類:
class fighter {
private:
int m_iHealth;
int m_iSpeed; // 數值越大速度越慢
const int m_iHitPoint;
char m_strName[260];
public:
fighter(const char* strName);
void hit(fighter* pfighter);
bool isAlive();
};
這個類雖然能抽象出基本數據類型,但存在明顯問題:
C++ 的繼承機制完美解決了上述問題。繼承表達的是 IS-A 關係(派生類 IS-A 基類),類似現實中的「狗是動物」。在 UML 中稱為泛化關係。當需要實現一系列相似但有差異的類別時,繼承是最佳選擇。
重構後的代碼結構:
class actor { // 基類
protected:
int m_iHealth;
const int m_iSpeed;
const int m_iHitPoint;
char m_strName[260];
public:
actor(const char* strName, const int iHealth, const int iSpeed, const int iHitpoint);
int& Health() { return m_iHealth; }
const char* getName() { return m_strName; }
virtual void hit(actor* Actor) = 0; // 純虛函數
bool isAlive();
};
class fighter : public actor { /* 具體實現 */ };
class knight : public actor { /* 具體實現 */ };
class warrior : public actor { /* 具體實現 */ };
protected 介於 public 和 private 之間:對外部不可見,但對繼承體系向下可見。派生類可直接存取基類的 protected 成員,這是繼承體系內部的「公共介面」。
虛函數是多態的基石。當基類指針指向派生類物件時,呼叫虛函數會動態綁定到派生類的實現:
actor* pA;
knight* pKnight = new knight;
pA = pKnight;
pA->hit(...); // 實際調用 knight::hit()
這種多態特性讓我們能用統一的基類介面管理不同角色集合:
vector troop1, troop2;
// 遍歷戰鬥邏輯,無需關心具體角色類型
while (it_tp1 != troop1.end() && it_tp2 != troop2.end()) {
(*it_tp1)->hit(*it_tp2);
// ...
}
繼承是面向對象思想中極重要卻也較難掌握的概念,COM 核心技術即基於多重繼承實現。真正融會貫通仍需長時間學習與實踐。文末附有完整可執行範例代碼,實現兩隊隨機角色戰鬥至一方全滅並輸出結果。