条款22:将成员变量声明为private
Declare data members private.
封装
首先,对于成员变量而言,如果成员变量不是public,那么用户唯一能访问对象的办法就是通过成员函数。
- 使用函数可以使得我们队成员变量的处理有这更精确的控制。
举个例子:
class AccessLevels {
public:
...
int getReadOnly() const { return readOnly; }
void setReadWrite(int val) { readWrite = val; }
int getReadWrite() const { return readOnly; }
void setWriteOnly(int val) { WriteOnly = val; }
private:
int noAccess; //对此int没有任何访问操作
int readOnly; //对此int做只读访问(read-only)
int readWrite; //对此int做读写访问(read-write)
int writeOnly; //对此int做唯写访问(write-only)
};
这是其中一个方面,另一个方面则是C++的一个重要特性:
- 封装!
如果我们想要通过函数访问成员变量,日后可改某个计算,来替换这个成员变量时,class用户一点也不会知道class的内部实现已经发生了改变。
再举个例子,对于汽车的自动测速程序,当汽车通过时,其速度被计算并填入一个速度收集器之中:
class SpeedDataCollection {
...
public:
void addVal(int speed); //添加一笔数据
double averageSoFar() const; //返回平均速度
...
};
对于上面的成员函数averageSoFar,它的实现可以有以下两种方法:
- 在class内设计一个成员变量,记录至今以来所有速度的平均值。当averageSoFar函数被调用时,直接返回对应的变量即可。
- 令averageSoFar每次被调用时都重新计算平均值,此函数有权查询收集器中的每一笔速度数据。
对于上面的第一种方法,缺点是会使得SpeedDataCollection对象变大,因为我们必须为用来存放目前平均值、积累总量、数据点数的每一个成员变量都分配空间。优点则是averageSoFar会非常的高效,它可以只是一个返回目前平均值的inline函数。
相反,对于第二种办法则,执行速度慢而SpeedDataCollection对象比较小。
到底哪种办法更好,这需要根据实际情况而定。重点是,因为通过成员函数来访问平均值,我们就相当于对其进行了封装,于是我们得以替换不同的实现方式。
将成员变量隐藏在函数接口之后,可以为“所有可能的实现”提供弹性。例如:
- 可以使得成员变量在被读或被写时轻松的通知其他对象;
- 可以验证class的约束条件以及函数的前提和事后状态;
- 可以在多线程换将中执行同步控制;
- 等等;
这种能力等价于其他语言中的“properties”,尽管额外需要一组小括号。
封装的重要之处在于,如果我们对用户隐藏成员变量(即封装),我们就可以确保class的约束条件总是会得到约束,因为只有成员函数可以影响它们。
我们同时也保留了日后变更实现的权利。如果我们不隐藏它们,即使拥有class原始码,改变任何public事物的能力还是极端受到束缚的,因为这样会破坏太多原始码。
public意味着不封装,而不封装意味着不可改变,特别是广泛使用的classes而言。
因此:
- 被广泛使用的classes是最需要封装的一个族群。
Protected
对于protected成员变量而言,论点也非常的相似。
“语法一致性”和“细微划分的访问控制”这些理由也同样适用于protected数据,就像对public一样。
然而,protected成员变量的封装性并非高于public成员变量。
首先,假设我们有一个public成员变量,而我们最终取消了它,那么所有使用它的客户码都会被破坏!,因此:
- public成员变量完全没有封装性。
然而,假设我们有一个protected成员变量,而我们最终取消了它,那么所有使用它的derived classes都会被破坏!因此:
- protected成员变量像public成员变量一样缺乏封装性。
一旦你将一个成员变量声明为public或protected而用户开始使用它,就很难改变这个变量所涉及的一切!总结说来,其实只有两种访问权限:
- private(提供封装)
- 其他(不提供任何封装)
最后: