C++|封裝類更易於使用、更改、調試並提高安全性

將程序中重複出現的代碼塊提煉成一個函數,也可以理解為一種封裝。如果將一個事物相關的數據及對這些數據的處理封裝起來,這就是類的概念。

豪無疑問,封裝可以實現代碼的重用、任務的分治、實現(implementaion)和接口(interfavce)的分離以及提高代碼的安全性。

1 封裝類更易於使用,並降低程序的複雜性。

封裝將對象實現的細節隱藏在對象的用戶之外。相反,對象的用戶通過公共接口訪問對象。這樣,用戶就可以使用對象,而不必瞭解它是如何實現的。簡單說,類由設計者完成實現,而類的使用者只需瞭解接口即可拿過來使用(實現和接口的分離)。正因為如此,我們才可以在編程語言中使用函數庫或類庫,而不是每一個程序都要從0開始。

對於完全封裝的類,您只需要知道哪些成員函數可以公開使用該類,它們採用什麼參數,以及返回什麼值。類是如何在內部實現的並不重要。例如,包含一系列名字的類可以使用C風格字符串的動態數組、std::vector、std::map、std::list或許多其他數據結構中的一個來實現。為了使用這個類,您不需要知道(或關心)某個是如何實現的。這大大降低了程序的複雜性,也減少了錯誤。這比任何其他原因都重要,這是封裝的關鍵優勢。

2 封裝類有助於保護數據並防止誤用

Global variables are dangerous because you don’t have strict control over who has access to the global variable, or how they use it. Classes with public members suffer from the same problem, just on a smaller scale.

全局變量是危險的,因為您無法嚴格控制誰可以訪問全局變量,或如何使用全局變量。有公共成員的類也有同樣的問題,只是規模較小。

例如,假設我們正在編寫一個字符串類。我們可以這樣開始:

class MyString
{
char *m_string; // we'll dynamically allocate our string here
int m_length; \t// we need to keep track of the string length
};

這兩個變量有一個內在的連接:m_length應該始終等於m_string持有的字符串的長度(此連接稱為不變量)。如果m_length是公共的,任何人都可以在不更改m_string的情況下更改字符串的長度(反之亦然)。這會使類處於不一致的狀態,這可能會導致各種奇怪的問題。通過將m_length_和m_string設置為私有,用戶被迫使用任何可用的公共成員函數來處理類(這些成員函數可以確保m_length_和m_string始終被適當設置)。

我們還可以幫助保護用戶避免在使用我們的類時出錯。考慮一個具有公共數組成員變量的類:

class IntArray 

{
public:
int m_array[10];
};

如果用戶可以直接訪問數組,則可以使用無效索引下標數組,從而產生意外結果:

int main()
{
IntArray array;
array.m_array[16] = 2; // invalid array index, now we overwrote memory that we don't own
}

但是,如果我們將數組設為私有,則可以強制用戶使用一個函數,該函數首先驗證索引是否有效:

class IntArray
{
private:
int m_array[10]; // user can not access this directly any more

public:
void setValue(int index, int value)
{
// If the index is invalid, do nothing
if (index < 0 || index >= 10)
return;

m_array[index] = value;
}
};

這樣,我們就保護了程序的完整性。順便說一下,std::array和std::vector的at()函數做了一些非常相似的事情!另外C風格字符串封裝為string類,裸指針封裝為智能指針都體現了同樣的思想。

3 封裝類更易於更改

舉個簡單的例子:

#include <iostream>

class Something
{
public:
int m_value1;
int m_value2;
int m_value3;
};

int main()
{
Something something;
something.m_value1 = 5;
std::cout << something.m_value1 << '\\n';
}/<iostream>

雖然這個程序運行良好,但如果我們決定重命名m_value1或更改其類型,會發生什麼情況?我們不僅會破壞這個程序,而且很可能大多數使用y類的程序也一樣!

封裝使我們能夠在不破壞所有使用類的程序的情況下更改類的實現方式。

下面是這個類的封裝版本,它使用函數訪問m_value1:

#include <iostream>

class Something
{
private:
int m_value1;
int m_value2;
int m_value3;

public:
void setValue1(int value) { m_value1 = value; }

int getValue1() { return m_value1; }
};

int main()
{
Something something;
something.setValue1(5);
std::cout << something.getValue1() << '\\n';
}/<iostream>

現在,讓我們更改類的實現:

#include <iostream>

class Something
{
private:
int m_value[3]; // note: we changed the implementation of this class!

public:
// We have to update any member functions to reflect the new implementation
void setValue1(int value) { m_value[0] = value; }
int getValue1() { return m_value[0]; }
};

int main()
{
// But our program still works just fine!
Something something;
something.setValue1(5);
std::cout << something.getValue1() << '\\n';
}/<iostream>

請注意,因為我們沒有更改類的公共接口中任何函數的原型,所以使用該類的程序將繼續工作,而不會發生任何更改。

4 封裝類更易於調試

最後,封裝有助於在出現問題時調試程序。通常當程序不能正常工作時,是因為我們的一個成員變量的值不正確。如果每個人都可以直接訪問該變量,那麼跟蹤修改該變量的代碼片段可能會很困難(可能是其中的任何一個,您需要對它們進行斷點操作才能確定是哪一個)。但是,如果每個人都必須調用同一個公共函數來修改一個值,那麼您可以簡單地中斷該函數並觀察每個調用方更改該值,直到您看到它出錯的地方。

-End-


分享到:


相關文章: