我一直想知道public
,protected
和private
属性的主题。我的记忆很容易回忆起我不得不破解某人的代码并且将被破解的类变量声明为private
总是令人不安的时候。
此外,有(更多)次我自己写了一门课,但从未意识到私有化财产的任何潜在收益。在这里我应该注意,使用公共变量不是我的习惯:我通过使用 getter 和 setter 来遵守 OOP 的原则。
那么,这些限制的重点是什么?
私有和公共的使用称为封装。软件包(类或模块)需要内部和外部是一个简单的见解。
外部(公共)是您与世界其他地方的合同。你应该尽量保持它简单、连贯、明显、万无一失,而且非常重要的是稳定。
如果您对优秀的软件设计感兴趣,规则很简单:将所有数据设为私有,仅在需要时公开方法。
隐藏数据的原则是一个类中所有字段的总和定义了对象的状态。对于编写良好的类,每个对象都应负责保持有效状态。如果国家的一部分是公开的,阶级永远不能提供这样的保证。
一个小例子,假设我们有:
class MyDate
{
public int y, m, d;
public void AdvanceDays(int n) { ... } // complicated month/year overflow
// other utility methods
};
您不能阻止该类的用户忽略 AdvanceDays() 并简单地执行以下操作:
date.d = date.d + 1; // next day
但是,如果您将y, m, d
所有 MyDate 方法设为私有并测试,则可以保证系统中只有有效日期。
重点是使用private
并protected
防止暴露您的班级的内部细节,以便其他班级只能访问您班级提供的公共“接口”。如果做得好,这可能是值得的。
我同意这private
可能是一个真正的痛苦,特别是如果你从库中扩展类。不久前,我不得不从Piccolo.NET框架中扩展各种类,令人耳目一新的是,他们将我需要的一切声明为protected
而不是private
,因此我能够扩展我需要的一切,而无需复制他们的代码和/或修改库. 从中获得的一个重要教训是,如果您正在为库或其他“可重用”组件编写代码,那么在声明任何内容之前您真的应该三思而后行private
。
关键字 private 不应该用于私有化您想要公开的属性,而是用于保护您的类的内部代码。我发现它们非常有用,因为它们可以帮助您定义代码中必须隐藏的部分,而这些部分可以被所有人访问。
我想到的一个例子是,当您需要在设置/获取私有成员的值之前进行某种调整或检查时。因此,您将创建一个具有某些逻辑的公共 setter/getter(检查某些内容是否为 null 或任何其他计算),而不是直接访问私有变量并且始终必须在代码中处理该逻辑。它有助于代码合同和预期的内容。
另一个例子是辅助函数。您可能会将一些较大的逻辑分解为较小的函数,但这并不意味着您希望每个人都看到和使用这些辅助函数,您只希望他们访问您的主要 API 函数。
换句话说,您想从界面中隐藏代码中的一些内部结构。
观看一些有关 API 的视频,例如这个 Google talk。
最近拥有能够从头开始设计和实现对象系统的极度奢侈,我采取了强制所有变量为(等价于)的策略protected
。我的目标是鼓励用户始终将变量视为实现的一部分,而不是规范。OTOH,我还留下了钩子以允许代码打破此限制,因为仍有理由不遵守它(例如,对象序列化引擎无法遵守规则)。
请注意,我的课程不需要强制执行安全性;该语言有其他机制。
在我看来,使用私有成员的最重要原因是隐藏实现,以便将来可以在不改变后代的情况下对其进行更改。
有些语言——例如 Smalltalk——根本没有可见性修饰符。
在 Smalltalk 的例子中,所有的实例变量总是私有的,所有的方法总是公开的。开发人员通过将方法放入“私有”协议中来表明方法的“私有”——可能会改变的东西,或者本身没有多大意义的辅助方法。
然后,类的用户可以看到他们应该三思而后行,向该类发送标记为私有的消息,但仍然可以自由使用该方法。
(注意:Smalltalk 中的“属性”只是 getter 和 setter 方法。)
首先,“属性”可以指不同语言中的不同事物。例如,在 Java 中,您指的是实例变量,而 C# 则区分了两者。
我将假设您的意思是实例变量,因为您提到了 getter/setter。
其他人提到的原因是封装。封装给我们带来了什么?
灵活性
当事情必须改变时(他们通常会改变),我们不太可能通过正确封装属性来破坏构建。
例如,我们可能决定进行如下更改:
int getFoo()
{
return foo;
}
int getFoo()
{
return bar + baz;
}
如果我们一开始没有封装“foo”,那么我们将有更多的代码需要更改。(比这一行)
封装属性的另一个原因是为我们的代码提供一种防弹方法:
void setFoo(int val)
{
if(foo < 0)
throw MyException(); // or silently ignore
foo = val;
}
这也很方便,因为我们可以在 mutator 中设置断点,这样我们就可以在尝试修改数据时中断。
如果我们的财产是公开的,那我们什么都做不了!