以前总是体会不到封装的好处。看书或者向他人咨询时,得到的答案都是:封装后可以修改类的内部实现,而无需修改使用了该类的客户代码;封装后可以对成员进行更精确的控制,例如将某个成员设置为只读的。但是这些都是理论,没有切身感悟。
最近在修改代码时遇到了一个相关问题,对封装的好处有了初步的体会。
假设现在有这样一个类:
- class Man
- {
- public:
- string name;
- string age;
- };
它拥有两个public的成员。当使用它的时候,会像这样:
- Man man;
- man.name = “Hotdog”;
- man.age = ”22”;
- class Man
- {
- public:
- string name;
- int age; //age成员改为int类型
- };
而相应的使用它的地方也需要修改,修改后类似这样:
- Man man;
- man.name = “Hotdog”;
- man.age = 22;
当然,在这个例子中使用Man类的地方只有一处,改动起来并不费时。而对于大型项目中的关键类,使用它的地方可能有成千上万处,将每处一一修改,是一个很大的工作量。
现在我们重新设计Man类,以求在修改类的内部实现时,无需修改客户代码。重新设计后的Man类将是这样:
- class Man
- {
- public:
- string getName() { return name; }
- void setName(string n) { name = n; }
- string getAge() { return age; }
- void setAge(string a) { age = a; }
- private:
- string name;
- string age;
- };
现在它依然拥有两个成员变量,name和age,不过现在这两个成员变量都是private的。它还拥有public的成员函数,用来获取和设置成员变量的值。现在,我们会像这样使用Man类:
- Man man;
- man.setName(“Hotdog”);
- man.setAge(“22”);
如果想把age成员修改为int类型,会像这样:
- class Man
- {
- public:
- string getName() { return name; }
- void setName(string n) { name = n; }
- string getAge() { stringstream ss; ss << age; return ss.str(); }
- void setAge(string a) { stringstream(a) >> age; }
- private:
- string name;
- int age; //age成员改为int类型
- };
至此,第一个问题算是告一段落,封装确实可以使我们容易地修改类的内部实现,而无需修改使用了该类的客户代码。
再用这个例子来看一看第二个问题:封装后可以对成员进行更精确的控制。Man类最初看起来是这样的:
- class Man
- {
- public:
- string name;
- int age;
- };
使用起来会是这样:
- Man man;
- man.name = “Hotdog”;
- man.age = 22;
当然了,这是“正常的”使用,但是客户有时候会不那么“正常”,有时候会粗心大意(特别是打瞌睡的时候),会错误地写出这样的代码:
- man.age = 222; //喔,不经意间多打了一个2
而Man类发现不了这样的问题,还是忠诚地接受了一个年龄为222的Man。这似乎看起来有些可笑。其实这个可笑的问题可以简单地避免——把成员变量封装起来:
- class Man
- {
- public:
- string getName() { return name; }
- void setName(string n) { name = n; }
- int getAge() { return age; }
- void setAge(int a)
- {
- if (age > 0 && age < 150)
- age = a;
- else
- cout << “error age input.” << endl;
- }
- private:
- string name;
- int age;
- };
现在,如果写出了
- man.age = 222;
这样的糊涂代码,程序就会打印出一条警告信息“errorage input.”。
另外,封装性还可以用于实现只读变量,例如:
- class Man
- {
- public:
- Man(string n, int a) { name = n; age = a; }
- string getName() { return name; }
- int getAge() { return age; }
- void setAge(int a) { age = a; }
- private:
- string name;
- int age;
- };
- getName(),而没有提供setName()。
- Man man(“Hotdog”, 22);
- man.setName(“Cooldog”); //Error! No such member function
- man.setAge(23); //OK
可见,封装后我们可以对成员进行更精确的控制,这样可以避免一些错误。