2009年1月7日 星期三

少用繼承

物件導向程式設計中,為了提升程式碼的再利用性,提供了繼承這樣的架構給我們使用,只要看不順眼的類別,就可以用繼承改寫舊功能,達到擴展的目的,範例如下:
class AI
{
private:
virtual void FSM(); // AI類別實做FSM
};

class Monster : public AI
{
private:
virtual void FSM(); // Monster類別改寫原來的函式
};

但就"程式碼再利用的"目的而言,事實上,我們常用的方法還有一種,"組成",利用物件彼此間的協力,完成特定工作,譬如,老闆要求去做一件A、B君都只會做部分功能的工作,依照繼承的觀念,是否要生成第三個類別C,繼承A、B的能力,然後改寫部分關聯而增加的程式碼? 然而我們是不是可以換個方向思考,我可以設計個類別C,工作交代C,C會先請A君做完他會做的,然後B繼續接手,一樣可以完成工作,與繼承兩者,事實上複雜度一樣,都需要3個類別,但是程式維護的小心程度卻是不可相提並論,程式如下

class AI
{
public:
void FSM();
};

class TimeControl
{
public:
void DoSomething();
};

用繼承:
class Npc:public AI, public TimeControl
{
void DoSomething(); // 這裡遭遇第一個難關
// 多重繼承,要小心AB的成員變數、成員函式設計關係
};

用組成:
class Npc
{
private:
AI ai;
TimeControl tc;

public:
void DoSomething(); // 我可以在這裡,靈活運用ai, tc物件的函式達成老闆目標
};

你可以看見,往後ABC三個類別的修改,相依性是否會降低很多很多,說白話,只要AB類別確定功能正常,發生BUG一定就是C,A類別新增怎樣的功能,不會影響C或B的運作,同理C增減怎樣的函式,AB都不會影響,而使用繼承呢? 就算你確定ABC三類別功能都正確,卻有可能因為繼承的關係,造成邏輯錯誤,像是C發生BUG的原因,是因為C修改了一個變數值,然後是A或B非常重要的變數,不允許由外部變更的(這裡有個小伏筆,避免修改到別人家的變數,變數請用private封裝...)。

加上有許多程式濫用繼承,一個遊戲專案裡頭隨便都成千上百個類別,加上彼此繼承的關係,專案像是N坨大肉粽樹,盤根錯節,某Z類別有BUG,原因卻是在千里之外八竿子打不到關係的A類別造成,改了A,卻連鎖反應要把某個肉粽樹的函式都要改過,想到就累人啊,所以,有得選的情形下,請少用繼承多用組成

再舉個生活上的例子,老闆要設計個簡報在客戶面前推銷商品,為了達到目標,他需要繼承文書小姐的能力+產品部門對商品規格特色了解的能力+簡報能力+....,或者,他可以把公司內文書小姐、產品部經理、行銷推廣部經理叫來,說出需求,接下來大家就自動會把事情搞定,而老闆則可專心挑毛病去,愛用繼承的,通常工作累得要死,愛用組成的,通常是老闆,你選哪個?

沒有留言:

張貼留言