wro*_*ame 24 language-agnostic oop class private-members
在面向对象编程中拥有类/结构的私有/受保护成员的目的是什么?让所有成员公开会有什么危害?
Pét*_*rök 27
封装.即隐藏您的类数据的实现.这允许您稍后更改它,而不会破坏所有客户端代码.例如,如果你有
class MyClass {
public int foo;
}
Run Code Online (Sandbox Code Playgroud)
你的客户可能会写代码
MyClass bar = new MyClass();
bar.foo++;
Run Code Online (Sandbox Code Playgroud)
现在如果你意识到它foo实际上应该是一个double而不是int,你可以改变它:
class MyClass {
public double foo;
}
Run Code Online (Sandbox Code Playgroud)
并且客户端代码无法编译:-(
通过精心设计的界面,内部(私有部分)的更改甚至可能包括将成员变量转换为计算,反之亦然:
class Person {
public String getName();
public String getStreetAddress();
public String getZipCode();
public String getCountryCode();
public int hashCode();
}
Run Code Online (Sandbox Code Playgroud)
(为简单起见,使用String属性 - 在现实世界的设计中,其中一些可能应该拥有自己的类型.)
使用此设计,您可以自由地在Address内部引入一个属性,其中包含街道地址,邮政编码和国家/地区代码,并重写您的访问者以使用此私有成员的字段,而无需您的客户注意任何事情.
您还可以自由决定是每次计算哈希码,还是将其缓存到私有变量中以提高性能.但是,如果该缓存字段是公共的,那么任何人都可以更改它,这可能会破坏哈希映射行为并引入细微的错误.因此封装是保证对象内部状态一致性的关键.例如,在上面的示例中,您的setter可以轻松验证邮政编码和国家/地区代码,以防止设置无效值.您甚至可以确保邮政编码格式对实际国家/地区有效,即确保跨多个属性的有效性标准.使用设计良好的界面,您可以通过例如仅提供设置器来同时设置两个属性来强制执行此绑定:
public void setCountryCodeAndZip(String countryCode, String zipCode);
Run Code Online (Sandbox Code Playgroud)
但是,对于公共字段,您根本就没有这些选择.
私有字段的特殊用例是不可变对象; 这在例如Java中很常见,示例是String和BigDecimal.这些类根本没有公共设置器,这保证了它们的对象一旦创建就不会改变它们的状态.这样可以实现大量的性能优化,并使它们更容易在多线程程序,ORM等中使用.
您可能想要阅读维基百科上的信息隐藏主题.
从本质上讲,私有成员允许类从外部使用者隐藏其实现细节.这允许类更好地控制数据和行为的表达方式,并允许消费者不知道与类的主要目的无关的细节.
隐藏实现细节可以防止类外部的代码建立对这些细节的依赖性,从而提高程序的可维护性.这允许实现独立于外部消费者而改变 - 降低了破坏现有行为的风险.当私有实现细节变为公开时,如果没有破坏依赖于这些细节的类的消费者的可能性,则不能更改它们.
私人成员还允许班级保护其实施免受外部滥用.通常,类的状态具有内部依赖关系,这些依赖关系定义状态何时有效 - 以及何时不存在.我们可以认为管理状态信息有效性的规则是不变的 - 这意味着类总是期望它们是真的.公开私有细节,允许外部代码以可能违反不变量的方式修改此状态,从而危及类的有效性(和行为).
信息隐藏的另一个好处是,它减少了类消费者必须理解的表面积,以便与类正确地交互.简化通常是件好事.它允许消费者专注于理解公共接口,而不是集体如何实现其功能.
在第7.4节:保护您的私有部分中很好地解释了这个在线C++教程.
为什么要烦恼这个东西?
说明符允许类非常复杂,具有许多成员函数和数据成员,同时具有其他类可以使用的简单公共接口.具有200个数据成员和100个成员函数的类可能非常复杂; 但如果只有三个或四个公共成员函数,其余都是私有的,那么有人可以很容易地学习如何使用该类.他只需要了解如何使用少数几个公共函数,而不需要打扰200个数据成员,因为他不允许访问这些数据.他只能通过类的公共接口访问私有数据.毫无疑问,在一个小程序中,使用这些说明符似乎是不必要的.但是,如果您计划进行任何合理大小的程序(超过几百行),它们是值得理解的.通常,将数据成员设为私有是一种好习惯.必须从类外部调用的成员函数应该是公共的,并且只从类中调用的成员函数(也称为"辅助函数")应该是私有的.这些说明符在涉及多个程序员的大型程序中特别有用.
以上说明解释了如何使用private简化学习曲线.这是一个解释"代码破坏"方面的例子:
这是一个ParameterIO读取和写入整数参数向量的类
class ParameterIO
{
public:
// Main member
vector<int> *Params;
string param_path;
// Generate path
void GeneratePath()
{
char szPath[MAX_PATH];
sprintf(szPath,"params_%d.dat",Params->size());
param_path = szPath;
}
// Write to file
void WriteParams()
{
assert_this(!Params->empty(),"Parameter vector is empty!");
ofstream fout(param_path.c_str());
assert_this(!fout.fail(),"Unable to open file for writing ...");
copy(Params->begin(),Params->end(),ostream_iterator<int>(fout,"\n"));
fout.close();
}
// Read parameters
void ReadParams(const size_t Param_Size)
{
// Get the path
Params->resize(Param_Size);
GeneratePath();
// Read
ifstream fin(param_path.c_str());
assert_this(!fin.fail(),"Unable to open file for reading ...");
// Temporary integer
for(size_t i = 0; i < Params->size() && !fin.eof() ; ++i) fin>>(*Params)[i];
fin.close();
}
// Constructor
ParameterIO(vector<int> * params):Params(params)
{
GeneratePath();
}
// Destructor
~ParameterIO()
{
}
// Assert
void assert_this(const bool assertion, string msg)
{
if(assertion == false)
{
cout<<msg<<endl;
exit(1);
}
}
};
Run Code Online (Sandbox Code Playgroud)
以下代码打破了这个类:
const size_t len = 20;
vector<int> dummy(len);
for(size_t i = 0; i < len; ++i) dummy[i] = static_cast<int>(i);
ParameterIO writer(&dummy);
// ParameterIO breaks here!
// param_path should be private because
// the design of ParameterIO requires a standardized path
writer.param_path = "my_cool_path.dat";
// Write parameters to custom path
writer.WriteParams();
vector<int> dunce;
ParameterIO reader(&dunce);
// There is no such file!
reader.ReadParams(len);
Run Code Online (Sandbox Code Playgroud)