面向对象语言类的语法几乎都有私有成员,为什么需要私有成员呢?最近在写一个类的时候,发现其他类在用到这个类的时候,基本都是要读取它的属性,一般我们会被教导说成员变量都设置为私有,通过getter和setter来获取和修改它们,但是当类的属性比较多时,太多的存取器会让这个类看起来非常庞大,直接将类的属性设置为公有,直接读取它不就好了吗,多省事,为什么很多的书都会建议你将数据成员设置为私有呢?
首先,当类的属性比较多时,一般这个类设计得不好,可以进一步拆分成更小的类。按照《代码整洁之道》的说法,类和函数都要尽量短小,短小的类一般会更加内聚,内聚的类意味着成员变量在大多数成员函数中被使用到。可以将联系紧密的成员变量抽离出来,新建一个类来使用它们。短小的类,其职责也更加明确,当别人阅读你的代码的时候,通过阅读类的名称,基本上就能知道这个类的功能。
其次,如果类的属性比较多,且经常需要给其它类直接读取修改,而且不好拆分,怎么办?这种类的对象更类似C语言中的结构体,或者JavaScript中的json,你只是想构造一种结构体来存储一坨数据,作为参数传递给其它函数或者类,并允许直接修改它们。在C++语言中,可以将这种类声明为struct类型,并不要给他们设置成员函数,明确表示它们是一种结构体。其它语言中可以尝试使用Map之类数据结构来代替,明确表达出它们是某种数据集合的含义。最喜欢TypeScript语言了,TypeScript语言的interface关键字,可以直接声明某种数据集合的类型,非常方便。
除此之外,对于类的某些属性,我们没有必要刻意把他们作为私有成员去处理。有些类的属性,可以预见在未来很长一段时间都是稳定的。比如说Image类,用来表示一张图片,图片有宽高属性,用cols和rows来表示。在使用Image类来表示一张图像的时候,我们没有必要通过getRows或者getCols接口来访问它们,rows和cols就是这个图像非常基本的一个属性。当然它们最好是只读类型的,不能直接修改,但是可以访问,一张特定图片的宽高属性是不变的。直接通过rows和cols访问,语意也更加明确。
class Image {
public:
Image(int r,int c):rows(r),cols(c){}
const int rows;
const int cols;
}
但是如果你的老板说,我要知道rows,cols属性被调用了多少次,你就得加一个count属性,在每个读取rows的地方对count属性自增1,就非常让人难受。如果通过类的getRows函数来获取rows,就可以在函数中对count+1,如下所示:
class Image {
private:
int rows;
int cols;
int countRows{0};
int countCols{0};
public:
Image(int r,int c):rows(r),cols(c){}
int getRows(){ countRows++; return rows; }
int getCols(){ countCols++; return cols; }
int getCounts() { return countRows + countCols; }
}
这样子,我们就没有必要在每个地方都countRows++,countCols++,因为你的需求变了,读rows和cols就不能直接读,要进行一定的处理,这就是给私有变量设置存取器的好处。同时在对私有成员进行赋值时,我们也可以对其进行校验等工作。
但是,我前面说了,rows和cols是非常稳定的属性,是一张特定图片非常基本的属性,没有哪个智障想知道rows和cols被调用了多少次吧,或者对这两个属性有什么想法,所以你直接将rows和cols设置为公有成员,没什么不好。
最后,我觉得private被设计出来的初衷,就是为了封装吧。暴露出来的接口应该是稳定的,而类中的变量随着需求的变动有可能会增删改,如果你没有将这些变量设置为私有,别人写代码的时候直接拿来用了,某天你把这个数据成员删掉,人家的代码就跑不动了,而你可能会觉得别人本来就不该直接用这个属性。就像电脑一样,使用电脑的人不需要知道什么是CPU、硬盘等,鼠标键盘会用就好了。CPU什么的就像类的私有变量,你不想被别人乱动,就把它封装到机箱里,只暴露显示器、键盘和鼠标等给用户调用。假如未来电脑不需要使用内存条,也与你无关,你依旧会使用电脑。
说了这么多,最后还是这个结论,平时养成好习惯,在设计类时public变量越少越好!