2012年4月4日 星期三

[筆記] 全文轉錄 - 類別的設計

 作者  ie945167 (龍蝦)                                      站內  Lobster
 標題  [筆記] 全文轉錄 - 類別的設計
 時間  2012/04/04 Wed 17:50:12
───────────────────────────────────────

http://imandrewliao.blogspot.com/2009/01/blog-post_16.html

類別是C++實踐物件導向程式的基礎,一般人對類別設計應該都有了些基本的概念,

這篇想要以系統開發的角度,來強調類別設計與使用的注意事項。


1. 變數封裝

基本上除了特別特別的需求,一般都建議你,把資料放在private區段,

為什麼呢,這樣我才可以設計函式,管控如何存取這些變數值,

避免變數被莫名奇妙的變更,

造成錯誤時,難以追蹤的困擾,

所以一個基本的變數,如果需要被外界存取,那需要一個設定函式,一個讀取函式,

如果不需要,內部使用,那一個都不需要提供。


可以放在protected嘛?基本上,類別設計,建議你忘記有這個區間的存在,為什麼?

protected區間可以被後續繼承類別存取,除非你有特殊用途,否則你埋下了一個炸彈,

你如何知道後續繼成出去的類別,不會胡亂更動其值?

不會又寫了一個函式,把資料public出去?


class NPC
{
    private:
    int m_iHp;  // 生命力,除了初值化外,不應該由外界來操作這個變數
    int m_iAcc; // 物理攻擊準確度,由企劃公式產生,更不應該由外界來操作
};


這樣的寫法,也斷絕了,系統開發時,有人妄想修改資料的念頭,減少將來系統長大時,

潛藏錯誤發生的機會,真的需要處裡內部數值時,再以下列方法處理:

class AStrangeClass
{
    private:
        NPC *m_pNpc;

    public:
        void SetNpc( NPC *npc ){ m_pNpc=npc; }
        NPC *GetNpc(){ return m_pNpc; }
};

關於全域變數呢,基本上建議,寫程式應該要把他視為罪惡,

這意味著有一個完全不能改的機制在那邊,無法控制其變數名稱,

只要跟這個變數有瓜葛的類別,形成了一個盤根糾結的系統,

如同先前我提到的,少用繼承一樣的結果,

將來改其中任何一個地方,潛在需要修改的程式是全部。


可是全域變數一定會用到的,這個地方建議去參考"Design Patterns"的Singleton樣式,

提供了一個將變數全域化,但卻被類別管控的方法。

2. 成員函式設計

成員函式設計,視目的而定,沒有一定的規範(除非專案本身的規定),

這裡建議,請在函式放置位置的時候,多思考一下,要放在哪個區段,

一般的建議是這樣,

如果函式需要被外界存取,放public(廢話...),

不需要的時候,請放private(廢話兩枚)。

是廢話嗎?回去隨便拿一個別人寫的類別看看,

是不是有很多函式被外界呼叫後,如果數值亂填,可能造成系統錯誤之類的?

你也許會反駁說,說明中有寫,這是內部使用的,錯誤是因為外界亂用啊?

可是我要說,設計類別的基本原則,就是讓人容易使用,且不容易出錯,

與其你放任函式public,裡頭寫一大堆exception,

為什麼不把這些函式private起來,錯誤發生率也小多了不是嗎?


常在程式之間流傳的對答:

啊,我知道bug發生原因了,是因為某某某呼叫了我一個函式,

而這個函式是內部使用的,不可以這樣呼叫...,



謹慎考慮多少可以減少潛藏可能發生問題的機會。


3. 注意別人類別的規範

接下來是使用別人類別的注意事項,不要企圖去改變回傳值的屬性,

原因是來自C++轉型的方便,許多人喜歡把參數或是回傳值,

亂轉型,變成其他的類型來操作,

這本是無可厚非的事情,最明顯的就是const回傳值,

但是,系統開發的時候,標明const,就是不希望你去改變這個數值,

嚴重的說來,改變這個值,搞不好會發生嚴重的錯誤,而經常有人就愛去碰:


class A
{
    public:
        const char *GetTypeName(){ return "this is a type"; }
};

void main()
{
    A a;
    const char *ptr=a.GetTypeName();
    strcpy( (char *)ptr, "this is not a type" );
}

編譯都對,執行的時候,就當給你看,所以

(a) 當你看見函式回傳值有const時,不要嘗試去改變回傳值內容

(b) 當函式宣告有throw時,請處理exception

    因為throw表示,函式內可能發生無法預期的錯誤,catch它,

    可以明瞭錯誤的原因,方便除錯

(c) 當類別裡面沒有虛擬函式時,不要嘗試去繼承它

    如果你寫個類別,會給後續類別繼承,你會怎麼寫?一定是在裡面寫一些虛擬函式,

    暗示後來的程式師,這幾個函式可能以後會有其他的寫法,

    如果你寫個類別,沒有任何虛擬函式,那表示什麼?

    不想被別人繼承?功能已經完備?不論哪個答案,都不該再有類別繼承它了。

    除此之外,還有一個原因,虛擬解構式,在繼承情況下,

    解構的記憶體才會正確的釋放,

    你的類別什麼虛擬函式都沒有(含解構式),繼承它,

    未來背負著一個memory leak的風險。

(d) 正確的繼承

    假設,CHuman是個基底類別,CHuman人物會動、可裝備、會說話....,

    假設遊戲中出現了一種東西叫"假人",人類經常習慣用名稱將之歸類為人類的一種,

    所以他該繼承CHuman,然後把所有不能用的功能重寫。

    你做了什麼事?把一個功能齊全的人類別,降級成什麼都不會的假人?

    只因為它長得像人?

    或者應該由一個物品,開始去繼承,增加功能比較適合?


    這裡提一個繼承基本上的判斷標準: 假人是不是一種人?

    意思是,人類會什麼,應該假人也要會什麼,少把類別寫成,假人是人的一種,

    但是他太多不會這不會那時...,大概就是錯誤的繼承關係了。



--
▅◣ Origin:  謠 言 報  bbs.csie.fju.edu.tw
▋◤ Author: ie945167 從 219-85-0-189-adsl-nei3.dynamic.so-net.net.tw 發表

沒有留言:

張貼留言