《C++ API 設計》ch2 Qualities

盡量不要讓看過的書像船過水無痕,嘗試用自己的話做小結。

這章的重點在:What are the basic qualities of a good API?

Model the problem domain

API 是用來解決某個問題的,問題可大可小。

API 要能對問題提供抽象化的概念,並且能夠將這抽象概念以 interface 表達出來。以 C++ 來說,我的理解是用 class name 以及 function name 表達抽象概念,也就是使用 API 的人應該要能從 class 及 function 看出抽象概念及使用邏輯。

一個問題沒有絕對正確的抽象化方式,重點是 API 要保有某種一致性及邏輯。

如果用物件導向來做 modeling 就會用 object modeling,也就是會定出 object、每個 object 做什麼、object 之間的關係與互動。一個 class 應該要定義為「做什麼」(what)而不是「怎麼做」(how)。

Hide implementation details

隱藏所有實作細節。如果沒藏好,以後要改實作,使用者可能跟著改到崩潰然後就不想用你的 API 了。(無誤)

interface 訂得好,底下怎麼搞都沒關係,改 interface 比較會影響到使用者。

隱藏實作細節的方法:

  • Declaration & Definition
    盡量在 header 只寫 declaration,implementation 放在 cpp。
  • Encapsulation
    • 請把 member 藏起來,外面需要它們的話使用 getter & setter。
    • 隱藏 class 內部才使用的 implementation method 們,API 使用者不需要知道 API 實際上如何實作出功能的。
    • 隱藏 class 內部才使用的 implementation class 們。
    • 所謂隱藏就是設成 private。

PS:這裡說的使用者是指使用 API 的人,通常是其他 programmer 或自己,不是使用軟體的一般使用者。

Minimally complete

API 功能要完整,可以滿足使用者需要的所有功能,要盡可能小但無法再更小了。

要小心 virtual function 可能會公開過多 function 給使用者。不過我還沒很懂這意思…

Easy to use

看到這裡總覺得在軟體設計上到處都有這句話,只是層次不太一樣。在 application 層級上,easy to use 希望的是一般使用者的好用。而在 API 層級上則是針對 API 使用者──通常是 programmer。

  • Discoverable
    使用者光看 API 就能自己心領神會、找出如何使用。
  • Difficult to misuse
    難以誤用,舉例:傳三個 enum Year、Month、Day 當參數比傳三個 int 不容易誤用。《Effective C++》好像說過同樣的話…
  • Consistent
    API 設計的一致性
  • Orthogonal
    • method 之間沒 side effect,例如 call 改變某屬性的 method 不會動到其他屬性。
    • 修改 API 的部分實作也不會影響到其他部分。
    • 做法:保持一種資訊只有一個地方有,不要到處 copy-paste code。盡量封裝,避免一個變數到處都可以 access 以致改了 A 可能就動到 B 的行為。
  • Resource allocation
    • 用 smart pointer 管理 memory。
    • 其他種類的 resource 也可以用 class 加以管理,object 的 construct 是 allocate resource,destruct 則是 release resource,通常稱為 RAII(Resource Acquisition Is Initialization)。舉例:Qt 的 QMutexLocker。
  • Platform independent
    避免在公開的 header 用針對特定平台的 #if#ifdef,例如 #ifdef _WIN32

鬆散耦合

跟 OO 的原則一樣,class 之間、module 之間不要黏太緊。

簡單看兩個 component A 跟 B 黏得緊不緊的方式是看改了 A 後 B 會不會改很多、A 看得到 B 多少東西(如只看得到 public function 還是也看得到 private member 等等)。

要避免兩個 component 互相依賴變成 dependency cycle,不然想用 A 就一定要有 B,但邏輯上可能根本不需要 B,看起來就很怪。

這邊提兩種鬆散耦合的方法:Manager class 跟事件通知。

Manager class

Manager class 會擁有並管理某些 class,例如與 output 有關的 class。

如果有一個 Manager class 管理 N 個 output class,其他 M 個 需要使用 output class 的 class 可以不需要跟所有 output class 都有關係(這樣會有 N * M 個關聯),大家可以只跟 Manager class 有關係(只剩 N + M 個關聯),從而降低 outpu class 與其他 class 間的耦合。

原來 Manager class 還有這種功能…

事件通知

在「某件事發生時需要通知其他人」的情境下,有以下幾種方式可以降低通知者與被通知者的耦合:

  • callback
    C 寫法,使用 function pointer,也可以有很多 callback。
  • observer
    Observer Pattern
  • notification
    不針對特定事件,比較像整個系統的機制,例如 signal-slot。

這些方法可以讓通知方與被通知方彼此不需要知道對方是誰,否則雙方得知道對方是誰才能通知,而當兩邊各有很多 class 的時候,那根本是場災難……

相關 note

Murmur

老覺得跟 OO 的原則很類似,很多概念都有在其他地方看過。不過現在再看,似乎更能配合實際經驗加以理解。