条款22:将成员变量声明为private
非常经典的常识性条款,这条款从上学的时候就有要求,在漫长的工作中逐渐变得清楚。
一、为什么声明为private
1、语法一致性
语法一致性内容请详见 条款18 ;
在这里说的语法一致性就是在说,如果成员变量声明为private,那么在public里面将只剩下成员函数,在客户使用该类的时候,不必在选择成员变量(不带括号)和成员函数(带括号)之间不断切换,可以剩下思考这些问题的时间。
当然,这一理由,并不算很有力。
2、对数据成员访问的精确控制
采用本书的例子:
class AccessLevels
{
public:
...
int getReadOnly()const { return readOnly; }
void setReadWrite(int value) { readWrite = value; }
int getReadWrite()const { return readWrite; }
void setWriteOnly(int value) { writeOnly = value; }
private:
int noAccess; //外部不能进行任何操作
int readOnly; //外部只读
int readWrite; //外部可读可写
int writeOnly; //外部只写
};
在这一例子中,使用函数就可以实现:
- 不可访问(no access)
- 只读访问(read only)
- 读写访问(read-write)
- 只写访问(write-only)
这种细微的划分访问控制是很有必要的,将是一个优秀的C++使用者的进阶功底。
3、封装
举个例子,假设正在写一个自动测试程序。当每汽车通过的时候,速度被计算出来,然后将结果保存在一个数据集中,这个数据集记录了迄今为止收集的所有速度数据。
class SpeedDataCollection {
...
public:
void addValue(int speed); // 增加一笔新数据
double averageSoFar() const; // 返回平均速度
...
};
当使用成员函数(averageSoFar)的方法,将会在以下几个方面体现优越性。
(1)隐藏实现细节:
私有成员变量隐藏了类的内部实现细节,使得外部代码无法直接访问这些变量,只能通过公共接口(如getter和setter方法)与它们交互。这有助于保护数据不被外部代码以不可预见的方式修改。
(2)提供访问控制:
通过将成员变量设为private,类的设计者可以精确控制哪些数据可以被外部访问或修改。例如,可以提供只读访问(通过const getter方法),或者完全限制访问(不提供任何访问方法)。
(3)促进接口与实现的分离:
封装鼓励将类的接口与其实现分离。这意味着客户端代码与类的内部实现细节解耦,客户端只通过类提供的接口与类交互。
(4)增强代码的可维护性和可扩展性:
由于外部代码不能直接访问私有成员变量,因此类的内部实现可以在不影响客户端代码的情况下进行修改或优化。这使得代码更易于维护和扩展。
(5)支持类的不变性:
封装可以帮助维护对象的不变性。通过限制对成员变量的直接访问,可以确保对象的状态在创建后不会被非法修改,从而保持其有效性和一致性。
(6)简化多线程编程:
在多线程环境中,封装可以简化同步机制的实现。类的实现者可以在访问私有成员变量的方法中加入必要的同步控制,而客户端代码不需要关心这些细节。
(7)允许灵活的实现变化:
如果成员变量是私有的,类的设计者可以在不改变类接口的情况下,自由地更改内部数据结构或算法。例如,可以在不通知使用类的客户端的情况下,从数组切换到链表来存储数据。
(8)减少错误和意外的副作用:
减少了由于外部代码直接操作内部状态而引起的错误和副作用。通过提供受控的访问方法,可以加入必要的检查和验证逻辑,确保数据的有效性和类的完整性。
(9)支持面向对象的设计原则:
封装是面向对象设计的基础原则之一,它支持其他原则,如单一职责原则和开闭原则。通过封装,类可以专注于其职责,同时对扩展开放,对修改关闭。
二、为什么不声明为protected
首先要明确 private 和 protected 的定义:
- private:私有成员只能在类的内部访问。它们不能被类的实例、友元类或友元函数之外的任何代码访问,包括继承的子类。
- protected:受保护的成员在类的内部和所有继承的子类中都是可见的。它们不能被类的实例或友元函数之外的非子类访问。
由此可见 protected 是破坏封装性的。
所以支持 ” 将成员变量声明为private “ 而不是 ” 将成员变量声明为protected“ 的理由,主要基于封装性、安全性和设计灵活性的考虑:
(1)封装性:
Private成员变量提供了更强的封装性。它们完全隐藏在类的内部,外部代码和继承的子类都无法直接访问这些变量。这有助于保护类的内部状态,防止外部代码以未预期的方式修改它们。
(2)安全性:
由于protected成员变量可以被子类访问和修改,这可能导致子类依赖于基类的内部实现细节,从而降低了类的安全性。如果基类的实现发生变化,可能会破坏子类的代码。而private成员变量则避免了这种情况。
(3)设计灵活性:
将成员变量设为private允许开发者在不破坏客户端代码的情况下更改类的内部实现。如果成员变量是protected,那么任何继承自该类的子类都可能依赖于这些成员变量,这限制了开发者对类实现的更改能力。
(4)避免子类错误使用:
子类可能会错误地使用或修改protected成员变量,这可能导致子类的状态不一致或违反基类的约束条件。而private成员变量则确保了只有基类自己才能访问和修改这些变量。
(5)减少子类对基类的依赖:
使用private成员变量可以减少子类对基类内部实现的依赖,鼓励子类通过基类提供的公共接口与基类交互,这有助于保持子类的独立性和可维护性。
(6)维护不变性:
Private成员变量有助于维护对象的不变性。由于外部代码和子类都不能直接修改这些变量,因此可以更容易地确保对象在创建后不会被非法修改。
三、总结
- 切记将成员变量声明为private。这可赋予客户访问数据的一致性、可细微性划分访问控制、允许约束条件获得保证,并提供class作者以充分的实现弹性
- protected并不比public更具封装性