在面向对象编程中拥有类/结构的私有/受保护成员的目的是什么?让所有成员都公开有什么害处?
10 回答
封装。即隐藏你的类数据的实现。这允许您稍后更改它,而不会破坏所有客户端代码。例如,如果你有
class MyClass {
public int foo;
}
您的客户可能会编写如下代码
MyClass bar = new MyClass();
bar.foo++;
现在,如果您意识到它foo
实际上应该是 double 而不是 int,您可以更改它:
class MyClass {
public double foo;
}
并且客户端代码无法编译:-(
使用设计良好的接口,内部(私有部分)的更改甚至可能包括将成员变量转换为计算,反之亦然:
class Person {
public String getName();
public String getStreetAddress();
public String getZipCode();
public String getCountryCode();
public int hashCode();
}
(为了简单起见,使用 String 属性——在现实世界的设计中,其中一些可能应该有自己的类型。)
通过这种设计,您可以自由地在内部引入例如Address
包含街道地址、邮政编码和国家/地区代码的属性,并重写您的访问器以使用此私有成员的字段,而您的客户不会注意到任何事情。
您还可以自由决定是每次都计算哈希码,还是将其缓存到私有变量中以提高性能。但是,如果该缓存字段是公开的,那么任何人都可以更改它,这可能会破坏哈希映射行为并引入细微的错误。所以封装是保证对象内部状态一致性的关键。例如,在上面的示例中,您的设置者可以轻松验证邮政编码和国家/地区代码,以防止设置无效值。您甚至可以确保邮政编码格式对实际国家/地区有效,即确保跨多个属性的有效性标准。使用设计良好的接口,您可以通过例如仅提供一个 setter 来同时设置两个属性来强制执行此绑定:
public void setCountryCodeAndZip(String countryCode, String zipCode);
但是,对于公共字段,您根本没有这些选择。
私有字段的一个特殊用例是不可变对象。这在例如 Java 中很常见,例如String
和BigDecimal
。这些类根本没有公共设置器,这保证了它们的对象一旦创建就不会改变它们的状态。这实现了许多性能优化,并使其更易于在多线程程序、ORM 等中使用。
您可能想阅读维基百科上的信息隐藏主题。
本质上,私有成员允许类对外部消费者隐藏其实现细节。这允许类更好地控制其数据和行为的表达方式,并允许消费者忽略与类的主要目的无关的细节。
隐藏实现细节可以防止类外部代码建立对这些细节的依赖关系,从而提高程序的可维护性。这允许实现独立于外部消费者进行更改 - 降低破坏现有行为的风险。当私有实现细节公开时,如果不破坏依赖这些细节的类的消费者,就无法更改它们。
私有成员还允许一个类保护它的实现免受外部滥用。通常,类的状态具有内部依赖关系,这些依赖关系定义状态何时有效 - 何时无效。我们可以认为控制状态信息有效性的规则是不变的——这意味着类总是期望它们为真。暴露私有细节,允许外部代码以可能违反不变量的方式修改此状态,从而损害类的有效性(和行为)。
信息隐藏的另一个好处是,它减少了班级的消费者为了与班级进行正确交互而必须了解的表面积。简化通常是一件好事。它允许消费者专注于理解公共接口,而不是类如何实现其功能。
很好地解释了第 7.4 节:保护这个在线 C++ 教程的私有部分。
为什么要纠结这些东西?
说明符允许一个类非常复杂,具有许多成员函数和数据成员,同时具有其他类可以使用的简单公共接口。一个有 200 个数据成员和 100 个成员函数的类写起来可能很复杂;但是如果只有三个或四个公共成员函数,其余的都是私有的,那么有人可以很容易地学习如何使用该类。他只需要了解如何使用一小部分公共函数,不需要理会这两百个数据成员,因为他不被允许访问这些数据。他只能通过类的公共接口访问私有数据。毫无疑问,在一个小程序中,使用这些说明符似乎是不必要的。然而,如果你打算做任何大小合理的程序(超过几百行),它们是值得理解的。通常,将数据成员设为私有是一种很好的做法。必须从类外部调用的成员函数应该是公共的,而只能从类内部调用的成员函数(也称为“帮助函数”)应该是私有的。这些说明符在涉及多个程序员的大型程序中特别有用。
上面的解释解释了使用如何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);
}
}
};
以下代码破坏了此类:
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);
打个比方,将私人成员公开为公众就像在您的汽车仪表板上有一个选项,可以让您调整发动机油压。
出于显而易见的原因,汽车应该在内部(私下)管理它,并且应该避免用户直接弄乱它(封装)。
这真的取决于你的意识形态。这个想法是隐藏由于某种原因不应该公开的信息。
如果你有一个想要在线发布的库,很多人会下载它,有些人可能会在他们的代码中使用它。如果您将公共 API 保持在最低限度并隐藏实现细节,那么当您遇到错误或想要改进代码时,更新它的难度就会降低。
此外,例如,在 Java 中,您无法在不更改其可见性的情况下限制对成员变量的访问,因此您经常发现自己过早地创建了 getter 和 setter,并使变量本身成为私有或受保护的。例如,在 Python 中,这个问题不存在,因为您可以让 getter 和 setter 表现得像直接访问的变量(它们在那里被称为属性)。
最后,有时您需要具有需要一致状态才能使用的方法,并且如果直接访问会导致问题。
一条经验法则是:如果你暴露了某些东西,就会有人使用它。大多数情况下,他们会出于错误的原因使用它(即不是您打算如何使用它们)。在这种情况下,信息隐藏相当于武器柜上的儿童锁。
要添加到彼得的答案,假设您的班级存储了一个名称,并且您希望将其从使用单个名称字符串更改为名字字符串和姓氏字符串。如果您的成员是公共的,其他类可能会直接读取(或写入)名称变量,并且会在该变量消失时中断。
更不用说您可能根本不希望其他班级能够编辑您的成员。
有时您不想向所有人透露私人信息。例如,您不想让公众知道您的年龄,但您可能想告诉人们您是否超过 25 岁。
人体内有内脏器官的目的是什么?把所有器官都放在外面有什么害处?
确切地!
简短的回答是:因为你需要它们,所以你不能没有它们,你不能将它们暴露给每个人来修改和玩弄它们,因为这可能会杀死你(导致你的类不能正常运行)。
完全没有伤害,这取决于课程的听众和消费情况。让我再重复一遍,让它沉入其中。
完全没有伤害,这取决于课程的听众和消费情况。
对于许多需要一个月左右的一两个人的小型项目,完美地完成所有私有和公共定义可能会大大增加工作量。然而,在大型项目中,可能有多个团队并且团队在地理位置上并不在一起,因此在开始时就正确设计所有公共接口可以大大增加整个项目成功的可能性。
所以在你开始回答这个问题之前,你真的必须看看一个班级将如何被消费以及由谁来消费。同样,软件开发生命周期会有多长?是几个月吗?年?几十年?除了你之外,还有其他人在使用这个类吗?
类越“公开”(即消费和使用该类的人越多),确定一个可靠的公共接口并坚持使用它就越重要。
一个简短的例子:您可能需要确保该值的某些条件。在这种情况下,直接设置它可能会破坏这样的条件。
许多人争辩说“您可能不希望每个人都阅读它”,但我认为设置值的约束是一个更有用的例子。