为什么我们需要吸气剂?

2024-04-28 20:05:04 发布

您现在位置:Python中文网/ 问答频道 /正文

我已经阅读了stackoverflow页面,其中讨论了"Why use getters and setters?",我已经被使用setter的一些原因所说服,例如:稍后的验证、数据封装等。但是使用getter的原因是什么?我认为获取私有字段的值有任何危害,也没有理由在获取a字段值之前进行验证。永远不使用getter而总是使用点表示法获取字段的值可以吗?


Tags: and数据危害use原因页面stackoverflowsetter
3条回答

如果Java类中的给定字段在读取时是可见的(在表达式的右上角),那么它还必须能够分配该字段(在表达式的LHS上)。例如:

class A {
    int someValue;
}

A a = new A();
int value = a.someValue; // if you can do this (potentially harmless)
a.someValue = 10;        // then you can also do this (bad)

除了上述问题,在类中使用getter的一个主要原因是为了保护该类的使用者不受实现细节的影响。getter不一定要简单地返回值。它可以返回一个从集合中提取的值或其他完全的值。通过使用getter(和setter),我们使类的使用者不必担心实现随时间而变化。在

在java中,不能告诉编译器允许从外部对公共字段进行只读访问。在

因此,公开公共领域为不受控制的修改打开了大门。在

我还没看到你在实践中的优势。在

显而易见的概念上的好处是,setter和getter可以在不影响外部世界的情况下使用这些函数进行更改。Java特有的另一个好处是,所有未标记为final的方法都可以被重写,因此您可以让子类重写该行为,这是额外的好处。在

过度杀戮?在

然而,你可能已经听过这些概念上的好处了,对于你日常生活中的情景来说,这听起来似乎有些过头了。理解软件工程实践的一个困难部分是,它们通常被设计成处理由开发人员团队管理的非常真实的、大规模的代码库。当你只是在自己的一个小项目上工作的时候,很多事情在开始的时候都会显得太过火了。在

让我们进入一些实际的,真实的场景。我以前在一个非常大规模的代码库工作。它是一个低级别的C代码库,有着悠久的历史,有时甚至比汇编语言高出一步,但我在那里学到的很多东西都能翻译成各种语言。在

现实世界的悲伤

在这个代码库中,我们有很多bug,其中大多数与状态管理和副作用有关。例如,我们有这样的情况,一个结构的两个字段应该保持同步。一个字段的有效值范围取决于另一个字段的值。但是我们遇到了两个字段不同步的bug。不幸的是,由于它们只是具有非常全局范围的公共变量(“global”实际上应该被视为是可以访问变量的代码量的一个程度,而不是绝对的),因此可能有数万行代码可能是罪魁祸首。在

作为一个更简单的例子,我们有过这样的情况:一个字段的值从来都不应该是负的,但是在我们的调试会话中,我们发现了负值。让我们把这个不应该是负的值称为x。当我们发现由x引起的缺陷为阴性时,x被任何东西碰过很久了。因此,我们花了几个小时放置内存断点,并试图通过查看所有可能以某种方式修改x的地方,在大海捞针。最终我们发现并修复了这个bug,但是这个bug应该在几年前就被发现了,而且修复起来应该不会那么痛苦。在

如果代码库的大部分不是直接访问x,而是使用了set_x之类的函数,那么情况就是这样了。如果是这样的话,我们可以做一些简单的事情:

void set_x(int new_value)
{
    assert(new_value >= 0);
    x = new_value;
}

。。。我们会立即发现凶手,并在几分钟内修复它。相反,我们是在这个bug被引入数年后才发现的,我们花了几个小时的时间才发现它并修复它。在

这就是我们可以为忽视工程智慧而付出的代价,在解决了第10000个问题之后,如果你的头发还没有完全变白,那么通过简单的方法(比如依赖函数而不是整个代码库中的原始数据)就可以避免这个问题,你通常还是不会有一个愉快的性格。在

getter和setters的最大价值来自setters。您通常最想控制的是状态操作,以防止/检测错误。仅仅因为需要setter修改数据,getter就变成了必需的。然而,当你想以非侵入性的方式(通过改变一个函数的实现)将原始状态转换为计算时,getter有时也很有用

界面稳定性

在你职业生涯的早期最难理解的事情之一是接口稳定性(防止公共接口不断变化)。只有在规模较大的项目中,以及可能与第三方的兼容性问题时,才能理解这一点。在

当你自己在做一个小项目时,你也许可以把类的公共定义改为你的核心内容,并重写所有的代码,用它来更新你的更改。不断地以这种方式重写代码似乎没什么大不了的,因为使用接口的代码量可能很小(例如:使用类的几百行代码,以及您自己编写的所有代码)。在

当你在一个大规模的项目中工作时,低头看数百万行代码,改变一个被广泛使用的类的公共定义可能意味着需要使用该类来重写100000行代码。很多代码甚至都不是你自己的代码,所以你必须侵入性地分析和修复别人的代码,并可能与他们密切合作来协调这些更改。其中一些人甚至可能不在你的团队中:他们可能是为你的软件编写插件的第三方,或者是已经转到其他项目的前开发人员。在

您确实不想反复遇到这种情况,所以设计足够好的公共接口以保持它们的稳定(不变)成为您最核心的接口的一项关键技能。如果这些接口正在泄漏实现细节,比如原始数据,那么反复修改它们的诱惑将是您一直面临的一个场景。在

因此,你通常希望设计接口时把重点放在“应该做什么”上,而不是“如何”上,因为“如何”可能比“什么”变化更频繁。例如,也许函数应该在列表中追加一个新元素。但是,您可能希望将它使用的列表数据结构替换为另一个,或者引入一个锁来使该函数线程安全(“how”关系)。如果这些“如何”关注点没有泄漏到公共接口,那么您可以在本地更改该类的实现(它是如何工作的),而不会影响请求它执行操作的任何现有代码。在

你也不希望类做太多的事情而变得单一,因为那样你的类变量将变得“更全局”(甚至在类的实现中对更多的代码可见),而且当一个稳定的设计已经做了这么多(类做得越多,它们就越想做)时,也很难找到一个稳定的设计。在

getter和setter并不是此类接口设计的最佳示例,但它们确实避免了暴露那些“how”细节,至少比公开的变量好一点,因此更改(中断)的理由更少。在

实际避免吸气剂/定型剂

永远不使用getter而总是使用点表示法获取字段的值可以吗?

有时候这是可以的。例如,如果您正在实现一个树结构,并且它将一个节点类用作客户机永远不会直接使用的私有实现细节,那么过于努力地专注于这个节点类的工程设计可能会变得适得其反。在

你的节点类不是公共接口。这是树的私有实现细节。您可以保证它不会被树实现以外的任何东西使用,所以应用这些类型的实践可能会有点过头。在

您不想忽略这些实践的地方是real公共接口,即树接口。你不想让树被误用并处于无效状态,你也不想看到一个不稳定的接口,在树被广泛使用之后很长一段时间内,你总是试图改变它。在

另一种情况是,如果你只是在做一个废品项目/实验作为一种学习练习,那么你一定知道你写的代码是一次性的,永远不会被用于任何规模的项目,也不会发展成任何规模的项目。在

尽管如此,如果您对这些概念非常陌生,我认为即使对于您的小规模项目来说,使用getter/setter错误也是一个有用的练习。这和宫城先生让丹尼尔·桑粉刷篱笆、洗车等类似,但丹尼尔·桑发现,在这些事情上,他的手臂已经筋疲力尽,这一切毫无意义。海山先生没有意识到他是怎么用间接拳的。在

相关问题 更多 >