今天受 poyenc 開示關於物件的抽象化以至於真正的 type safe ,撰文以誌之。
從一個簡單的結構開始。今天要你實作一個關於「人」的數據資料,大部分最直觀,「表面上」最省力的地方是簡單的如下宣告:
struct Person {
std::string name;
std::string id;
int age;
};
但要回頭想一想難道這真的表達了我們想要的資料結構嗎?對名字、身分證字號、年紀這三者用以上的變數宣告都太攏統,也不安全。如果要把這個結構導入系統中,會有許多不符合使用情境卻被合法的裝進 struct Person
結構中的情況發生。
不合法情況像是:
id
身分證字號應該由 1 個英文字與 9 個數字組成,其餘為不合法身分證字號age
不應該出現負數
對一個初次閱讀這段程式碼的人,更不會知道該如何使用這個結構,怎麼樣才是合法的程式碼。
有了 member type 這種事情,可以讓使用 struct Person
的人遠離變數型態的煩惱。他不需要知道在 *_type
底下是什麼樣的結構。他可以根據他對 name
, id
, age
這些型態的認知來操縱這個變數。這樣僅做到抽象化的動作,他跟上面的程式碼片段別無二致,仍然是不安全的。
struct Person {
// member type
using name_type = std::string;
using id_type = std::string;
using age_type = int;
// variable declaration
name_type name;
id_type id;
age_type age;
};
要做到「真正的」type safe ,我們就需要把問題拿到手上並動手解決。對型態都要有明確定義, 從 constructor, destructor, copy operator, move operator 等等⋯⋯都要自己親自去 overload 。當然會比較費工,但是要做到這個地步才能確保他完全安全。
class Name : private std::string {
// 台灣人名格式
};
class taiwanId : private std::string {
// 台灣身分證格式
// - 限制只能有 1 個英文字元
// - 英文字元後接 9 個數字
};
class Age : private int {
// 年紀 >=0
};
struct Person {
using name_type = Name;
using id_type = taiwanId;
using age_type = Age;
name_type name;
id_type id;
age_type age;
};
可能你還好奇為什麼要用 using
來把這些 type 再包一層,因為這又是一層抽象化!今天假設你想要換成美國的 social security number ,這時 ID 的格式就改變。但好險你在 struct Person
底下對 ID 的操作都是根據 id_type
來做,所以只要更改 id_type
等號右邊的結構,就可以換成美國人的身分證結構了,增加了程式碼的可攜性。
struct Person {
using name_type = Name;
using id_type = usaId; // 只需要改這裡
using age_type = Age;
name_type name;
id_type id;
age_type age;
};
總結以上有兩個重點:
- 真正的 type safe 要解決隱含的資料結構問題就需要繼承到自己手上解決
- member type 可以增加程式碼的可攜性
BTW 寫出優質的程式碼這檔事大學沒教真的是太可惜了!