[019] C++ Class 的設計考量(一)

[019] C++ Class 的設計考量(一)

昨天 談到函數(Function):「好的函數不該有副作用」。今天來談物件,以及生成物件的類別(Class)。

  1. 介面設計成讓使用端不容易誤用。
  2. 少用繼承(Inheritance, is-a 關係),善用合成(Composition, has-a 關係)。
  3. 內部實作露出越少越好。

介面設計成讓使用端不容易誤用

Virtual Function 為例,底下 Animal 類別有一個 Pure Virtual Function - MakeSound:

class Animal {
private:
  virtual void MakeSound() = 0;
};

因為 MakeSound 為 Pure Virtual,故必須要繼承 Animal 實作並覆寫(Override)MakeSound 成員函數才有辦法生成 Animal 物件。又因 MakeSound 的存取權限為 private,所以 Derived Class 無法呼叫 Animal class 的 MakeSound,也就是說,Derived Class 必須靠自己實作出 MakeSound 函數,不能借用 Animal::MakeSound

透過這樣的設計規範,讓 Derived Class 無法寫出不合理的程式碼。考慮以下兩個繼承 Animal 之 Dog, Cat:

class Dog : public Animal {
private:
  void MakeSound() override { Bark(); }
  void Bark() {}
};
class Cat : public Animal {
private:
  void MakeSound() override { Meow(); }
  void Meow() {}
};

不管 Animal::MakeSound 有沒有實作,Dog 都無法呼叫它,也就保證 Dog::MakeSound 是由 Dog 實作,跟 Animal 沒有關係,出問題較容易釐清責任

Animal 類別可以設計一函數呼叫 MakeSound

void Animal::OnStarving() {
  MakeSound();
}

肚子餓的時候會發出何種聲音(Bark or Meow),直到執行時期(Runtime)才會知道。適時利用這種抽象的概念,可以設計出強大,好用,又不容易出錯的類別。(不少 UI Framework 仰賴這樣的設計)