Item 22: Declare data members private
數據成員聲明為私有可以提供一致的接口語法,提供細粒度的訪問控制,易於維護類的不變式,同時可以讓作者的實現更加靈活。而且我們會看到,protected
並不比public
更加利於封裝。
你肯定也遇到過這種困惑,是否應該加括號呢?
obj.length // 還是 obj.length()?
obj.size // 還是 obj.size()?
總是難以記住如何獲取該屬性,是調用一個getter
?還是直接取值?如果我們把所有數據都聲明為私有,那麼在調用語法上,統一用括號就好了。
為數據成員提供getter和setter可以實現對數據更加精細的訪問控制,比如實現一個只讀的屬性:
class readOnly{
int data;
public:
int get() const { return data; }
}
事實上,在C#中提供了訪問器(又稱屬性)的概念, 每個數據成員都可以定義一套訪問器(包括setter和getter),使用訪問器不需要使用括號:
public class readWrite{
private string _Name;
public string Name{
set { this._Name = value; }
get { return this._Name; }
}
}
ReadWrite rw;
// 將會調用set方法
rw.Name = alice;
封裝所有的數據可以方便以後類的維護,比如你可以隨意更改內部實現,而不會影響到既有的客戶。例如一個SpeedDataCollection
需要給出一個平均值:
class SpeedDataCollection{
public:
void add(int speed);
double average() const;
};
average()
可以有兩種實現方式:維護一個當前平均值的屬性,每當add
時調整該屬性;每次調用average()
時重新計算。兩種實現方式之間的選擇事實上是CPU和內存的權衡,如果在一個內存很有限的系統中可能你需要選擇後者,但如果你需要頻繁地調用average()
而且一點內存不是問題,那麼就可以選擇前者。
你的實現方式的變化不會影響到你的客戶。但如果avarage()
不是方法而是一個共有數據成員。 那麼對於你的每次實現方式變化,客戶就必須重新實現、重新編譯、重新調試和測試它們的代碼了。
既然共有數據成員會破壞封裝,它的改動會影響客戶代碼。那麼protected
呢?
面向對象的精髓在於封裝,可以粗略地認為一個數據成員的封裝性反比於它的改動會破壞的代碼數量。比如上述的average
如果是一個public
成員,它的改動會影響到所有曾使用它的客戶代碼,它們的數量是大到不可知的(unknowably large amount)。如果是protected
成員,客戶雖然不能直接使用它,但可以定義大量的類來繼承自它,所以它的改動最終影響的代碼數量也是 unknowably large。
protected
和public
的封裝性是一樣的!如果你曾寫了共有或保護的數據成員,你都不能隨意地改動它們而不影響客戶代碼!