现在的位置: 首页 > 综合 > 正文

封装的好处

2018年06月06日 ⁄ 综合 ⁄ 共 2791字 ⁄ 字号 评论关闭

以前总是体会不到封装的好处。看书或者向他人咨询时,得到的答案都是:封装后可以修改类的内部实现,而无需修改使用了该类的客户代码;封装后可以对成员进行更精确的控制,例如将某个成员设置为只读的。但是这些都是理论,没有切身感悟。

最近在修改代码时遇到了一个相关问题,对封装的好处有了初步的体会。

假设现在有这样一个类:

  1. class Man  
  2. {  
  3. public:  
  4.   string name;  
  5.   string age;  
  6. };  

它拥有两个public的成员。当使用它的时候,会像这样:

  1. Man man;  
  2. man.name = “Hotdog”;  
  3. man.age = ”22”;  

这样一来,我们若是修改了Man类的实现,例如需要把age成员改为int类型,那么Man类现在看起来就会是这样:

  1. class Man  
  2. {  
  3. public:  
  4.   string name;  
  5.   int age;  //age成员改为int类型  
  6.  };  

而相应的使用它的地方也需要修改,修改后类似这样:

  1. Man man;  
  2. man.name = “Hotdog”;  
  3. man.age = 22;  

当然,在这个例子中使用Man类的地方只有一处,改动起来并不费时。而对于大型项目中的关键类,使用它的地方可能有成千上万处,将每处一一修改,是一个很大的工作量。

现在我们重新设计Man类,以求在修改类的内部实现时,无需修改客户代码。重新设计后的Man类将是这样:

  1. class Man  
  2. {  
  3. public:  
  4.   string getName() { return name; }  
  5.   void setName(string n) { name = n; }  
  6.   string getAge() { return age; }  
  7.   void setAge(string a) { age = a; }  
  8. private:  
  9.   string name;  
  10.   string age;  
  11. };  

现在它依然拥有两个成员变量,name和age,不过现在这两个成员变量都是private的。它还拥有public的成员函数,用来获取和设置成员变量的值。现在,我们会像这样使用Man类:

  1. Man man;  
  2. man.setName(“Hotdog”);  
  3. man.setAge(“22”);  

如果想把age成员修改为int类型,会像这样:

  1. class Man  
  2. {  
  3. public:  
  4.   string getName() { return name; }  
  5.   void setName(string n) { name = n; }  
  6.   string getAge() { stringstream ss; ss << age; return ss.str(); }  
  7.   void setAge(string a) { stringstream(a) >> age; }  
  8. private:  
  9.   string name;  
  10.   int age;  //age成员改为int类型  
  11. };  

这次只修改了getAge()和setAge()的内部实现,而没有修改接口,因此无需修改使用Man类的地方。

至此,第一个问题算是告一段落,封装确实可以使我们容易地修改类的内部实现,而无需修改使用了该类的客户代码

再用这个例子来看一看第二个问题:封装后可以对成员进行更精确的控制。Man类最初看起来是这样的:

  1. class Man  
  2. {  
  3. public:  
  4.   string name;  
  5.   int age;  
  6.  };  

使用起来会是这样:

  1. Man man;  
  2. man.name = “Hotdog”;  
  3. man.age = 22;  

当然了,这是“正常的”使用,但是客户有时候会不那么“正常”,有时候会粗心大意(特别是打瞌睡的时候),会错误地写出这样的代码:

  1. man.age = 222;  //喔,不经意间多打了一个2  

而Man类发现不了这样的问题,还是忠诚地接受了一个年龄为222的Man。这似乎看起来有些可笑。其实这个可笑的问题可以简单地避免——把成员变量封装起来:

  1. class Man  
  2. {  
  3. public:  
  4.   string getName() { return name; }  
  5.   void setName(string n) { name = n; }  
  6.   int getAge() { return age; }  
  7.   void setAge(int a)   
  8.   {   
  9.     if (age > 0 && age < 150)   
  10.       age = a;   
  11.     else  
  12.       cout << “error age input.” << endl;  
  13.   }  
  14. private:  
  15.   string name;  
  16.   int age;  
  17. };  

现在,如果写出了

  1. man.age = 222;  

这样的糊涂代码,程序就会打印出一条警告信息“errorage input.”。

另外,封装性还可以用于实现只读变量,例如:

  1. class Man  
  2. {  
  3. public:  
  4.   Man(string n, int a) { name = n; age = a; }  
  5.   string getName() { return name; }  
  6.   int getAge() { return age; }  
  7.   void setAge(int a) { age = a; }  
  8. private:  
  9.   string name;  
  10.   int age;  
  11. };  

这样的话,当构造出一个Man对象后,name就成了只读的,因为这里只提供了

  1. getName(),而没有提供setName()。  
  2. Man man(“Hotdog”, 22);  
  3. man.setName(“Cooldog”); //Error! No such member function  
  4. man.setAge(23);     //OK  

可见,封装后我们可以对成员进行更精确的控制,这样可以避免一些错误。

抱歉!评论已关闭.