天天看點

讀書筆記 effective c++ Item 22 将資料成員聲明成private

我們首先看一下為什麼資料成員不應該是public的,然後我們将會看到應用在public資料成員上的論證同樣适用于protected成員。最後夠得出結論:資料成員應該是private的。

1. 為什麼資料成員不能是public的?

為什麼資料成員不能夠是public的?

2.1 一緻性

讓我們從句法的一緻性開始(

Item 18

)。如果資料成員不是Public的,那麼客戶通路對象的唯一方法就是通過成員函數。如果所有的公共接口都是函數,客戶就不必記住通路一個類的成員時是否使用括号了。這友善了客戶的使用。

2.2 對資料成員通路的精确控制

如果一緻性沒有讓你信服,那麼使用函數可以使你對資料成員的通路有更加精确的控制呢?如果你将資料成員聲明成public的,每個人對其都有讀寫權限,但是如果你使用函數來對值進行擷取(get)或者設定(set),你就可以實作不可通路(no access),隻讀通路(read only)和讀寫(read-write)通路。如果你需要,你甚至可以實作隻寫(write-only)通路:

1 class AccessLevels {
 2 
 3 public:
 4 
 5 ...
 6 
 7 int getReadOnly() const { return readOnly; }
 8 
 9 void setReadWrite(int value) { readWrite = value; }
10 
11 int getReadWrite() const { return readWrite; }
12 
13 void setWriteOnly(int value) { writeOnly = value; }
14 
15 private:
16 
17 int noAccess; // no access to this int
18 
19 int readOnly; // read-only access to this int
20 
21 int readWrite; // read-write access to this int
22 
23 int writeOnly; // write-only access to this int
24 
25 };      

這種細粒度的通路控制是很重要的,因為許多資料成員應該被隐藏起來。很少情況下需要所有的資料成員都有一個getter和一個setter。

2.3 封裝

仍然沒有說服力?該是使出殺手锏的時候了:封裝。如果你通過一個函數來實作對一個資料成員的通路,日後你可能會用計算來替代資料成員,使用你的類的任何客戶不會覺察出類的變化。

舉個例子,假設你在實作一個應用,自動化裝置使用這個應用來記錄通過車輛的速度。當每輛車通過的時候,速度被計算出來,然後将結果儲存在一個資料集中,這個資料集記錄了迄今為止收集的所有速度資料:

1 class SpeedDataCollection {
 2 
 3 ...
 4 
 5 public:
 6 
 7 void addValue(int speed); // add a new data value
 8 
 9 double averageSoFar() const; // return average speed
10 
11 ...
12 
13 };      

現在考慮成員函數averageSoFar的實作。一種實作的方法是在類中定義一個資料成員,用來表示迄今為止所有速度資料的平均值。當averageSoFar被調用的時候,它隻是傳回這個資料成員的值。另外一種方法是在每次調用averageSoFar的時候重新計算平均值,這可以通過檢查資料集中的每個資料值來做到。

第一種方法使得每個SpeedDataCollection對象變大,因為你必須為儲存平均速度,累積總量以及資料點數量的資料成員配置設定空間。然而,averageSoFar可以被很高效的實作出來;它隻是一個傳回平均速度的内聯函數(見Item 30)。相反,在請求的時候才計算平均值會使得averageSoFar運作非常緩慢,但是每個SpeedDataCollection對象會比較小。

誰能确定哪個才是更好的呢?在一台記憶體吃緊的機器上,并且應用中對平均值的需要不是很頻繁,每次計算平均值可能會是一個更好的選擇。在一個對平均值需求頻繁的應用中,速度很重要,但記憶體充足,你可能更喜歡将平均速度儲存為資料成員。這裡的重要一點是通過一個成員函數來通路平均值(也就是将其封裝起來),你可以在這些不同實作之間來回切換,用戶端至多隻需要重新編譯就可以了。(通過Item31中描述的技術,你甚至可以不用重新編譯)

将資料成員隐藏在函數接口後邊可以靈活的提供不同種類的實作。舉個例子,它可以使下面這些實作變得很簡單:當資料成員被讀或者寫的時候通知其它對象;驗證類的不變性和函數的先置和後置條件;在多線程環境中執行同步等等。從其它語言(像Delphi和C#)轉到C++的程式員将會識别出來C++的這種功能同其它語言中的“屬性”是等同的,但是需要額外加一對括号。

封裝比它起初看起來要重要。如果你對客戶隐藏你的資料成員(也就是封裝它們),你就能夠確定類能一直維持不變性,因為隻有成員函數能夠影響它們。進一步來說,你保留了日後對實作決策進行變動的權利。如果你沒有将這些決策隐藏起來,你将會很快發現即使你擁有一個類的源碼,但是你修改public成員的能力是及其受限的,因為如果修改public成員,太多的客戶代碼會被破壞。Public意味這沒有封裝,更實際的講,未封裝意味這不能變化,特别對被廣泛使用的類更是如此。是以對廣泛使用的類最需要進行封裝,因為它們最能受益于将一個實作替換為一個更好的實作。

2. 為什麼資料成員不能是protected的?

上面的論證對于protected資料成員來說是類似的。事實上,它們是完全相同的,雖然一開始看上去不是這樣。在論證資料成員不能為public時,句法一緻性和細粒度通路控制這兩個原因同樣适用于protected成員,但是封裝呢?protected資料成員不是有比public資料成員更好的封裝性麼?令人感到吃驚的回答是,它們不是。

Item 23解釋了封裝性同一些東西發生變化引起的代碼可能被破壞的數量成反比。一個資料成員的封裝型,同資料成員發生變化引起的代碼可能被破壞的數量成反比,舉個例子,如果資料成員從類中移除。(可能被一個計算代替,正如在averageSoFar中實作的)。

假設我們有一個public資料成員,我們将其删除。有多少代碼會被破壞?所有使用它的客戶代碼将被破壞,一般情況下這應該是個未知的數量。Public資料成員是以完全沒有被封裝。但是假設我們有一個protected資料成員,我們将其删除。現在會有多少代碼被破壞呢?所有使用它的派生類,同樣的,這也是未知數量的代碼。Protected資料成員同public資料成員一樣也沒有被封裝,因為在兩種情況中,如果資料成員被修改,都有未知數量的客戶代碼會被破壞。這是違反直覺的,但是一個經驗豐富的庫實作人員會告訴你,這就是真的。一旦你将一個資料成員聲明成public或者protected并且客戶開始使用它,很難改變資料成員的任何東西。因為一旦修改了,太多的代碼會被重新實作,重新測試,重新編輯文檔,重新編譯。從封裝的角度來說,真的隻有兩種通路級别:private(提供了封裝)和其它的(沒有提供封裝)。

3. 總結

  • 将資料成員聲明為private。它為客戶提供了對資料句法一緻的通路,給予細粒度的通路控制,允許執行類的不變性,為類的作者提供實作的靈活性。
  • Protected沒有比public更具封裝型。

作者:

HarlanC

部落格位址:

http://www.cnblogs.com/harlanc/

個人部落格:

http://www.harlancn.me/

本文版權歸作者和部落格園共有,歡迎轉載,但未經作者同意必須保留此段聲明,且在文章頁面明顯位置給出,

原文連結

如果覺的部落客寫的可以,收到您的贊會是很大的動力,如果您覺的不好,您可以投反對票,但麻煩您留言寫下問題在哪裡,這樣才能共同進步。謝謝!

繼續閱讀