如果变量具有getter和setter,应该公开吗?


23

我有一个带有私有变量的类,该类具有该变量的getter和setter方法。为什么不将该变量公开?

我认为您唯一需要使用getter和setter的情况是,是否需要执行除set或get之外的其他操作。例:

void my_class::set_variable(int x){
   /* Some operation like updating a log */
   this->variable = x;
}

10
吸气剂和吸气剂在那里用于屏蔽。有一天,您可能想做this->variable = x + 5UpdateStatistics在setter中调用一个函数,在这种情况下classinstancea->variable = 5会引起问题。
编码器

1
另一个例子是:set_inchset_centimeterget_inchget_centimeter,一些怪异的行为。
rwong

是的,getter和setter可以帮助您以希望控制实例变量的方式来控制它们。
developerdoug

7
@Coder:您总是可以在需要时添加您的getter / setter吗?还是这是您在C ++中无法轻易完成的事情(我的经验主要是Delphi)?
Marjan Venema

这个问题:programmers.stackexchange.com/questions/21802/…可能引起人们的兴趣。
温斯顿·埃韦特

Answers:


21

您听说过物业吗?

属性是具有“内置”访问器(获取器和设置器)的字段。例如,Java没有属性,但是建议将getter和setter写入私有字段。C#具有属性。

那么,为什么我们需要吸气剂和吸气剂?基本上,我们需要它来保护/屏蔽现场。例如,您不在访问内存引用中的字段,而是在访问将更改字段(引用)的方法。像您的示例一样,该方法能够执行用户不愿进行的某些操作(封装行为)。例如,想象一下,有十二个类使用您的公共字段,并且您需要更改其使用方式...您必须查看每个类,才能更改它们使用字段的方式...不所以“ OOlysh”。

但是,例如,如果您有一个名为dead的布尔字段。在声明setDead和isDead之前,您应该三思。您应该编写易于阅读的访问器,例如kill()而不是setDead。

但是,有许多框架假定您遵循JavaBean命名约定(在此谈论Java),因此,在这种情况下,应遵循命名对流声明所有的getter和setter。


2
现在“人类可读”终于成为我可以“抱怨”的论点。我通常听到的所有其他论点都是某种未来的证明,当需要时也可以轻松解决...
Marjan Venema

13
您对“将来的证明”不屑一顾是常见的,但却是错误的。是的,如果您是该代码的唯一客户则可以在需要时解决此类更改。如果您从不为外部客户发布解决方案,那么您只需承担技术债务,而且没人需要知道。但是内部项目有一种习惯,就是在您最不期望的时候公开,然后对您不利。
Kilian Foth 2011年

2
您实际上提出了一个奇妙的观点。杀人不是一成不变的,而是行动。它完全隐藏了实现-这就是您编写OO的方式。另一方面,属性就像setters / getters一样愚蠢,甚至是愚蠢的,因为简单的语法鼓励人们在甚至不需要它们时就将它们添加到变量中(OO-Furbar等待发生)。谁会想到只需要向其死标志添加“属性”标签就使用kill()?
Bill K

1
该问题被标记为C ++问题。C ++不支持属性,那么为什么还要发布它呢?
Thomas Eding 2012年

1
@ThomasEding:C ++在具有可覆盖的赋值运算符方面有些独特。尽管C ++不像C#或VB.NET这样的语言使用“属性”一词,但这个概念仍然存在。在C ++中,可能会使用运算符重载,从而foo.bar = biz.baz+5调用on函数biz来求值baz,然后将其加5并调用on函数foo来设置bar结果值。这样的重载不称为“属性”,但它们可以达到相同的目的。
超级猫

25

这不是最受欢迎的意见,但我认为差别不大。

设置者和获取者是一个非常糟糕的主意。我已经考虑过了,说实话,在实践中我无法提出设置者/获取者属性和公共变量之间的区别。

在理论中,设置器和获取器或属性添加了一个位置,以在设置/获取变量时采取一些额外的操作,并且从理论上讲,它们将您的代码与更改隔离开来。

实际上,我很少看到使用setter和getter来添加操作,并且当您确实要添加操作时,想要将其添加到类的所有setter或getter中(例如日志记录),这应该使您认为应该成为更好的解决方案。

至于隔离设计决策,如果将int更改为long,则仍然必须更改setter并至少检查手动访问它们的每一行-那里没有太多隔离。

无论如何,默认情况下都应避免使用可变类,因此添加setter应该是最后的选择。使用构建器模式可以缓解这种情况,在该模式下可以设置值,直到对象处于所需状态,然后类可以变为不可变,并且设置器将引发异常。

至于吸气剂-我仍然无法在吸气剂和公共最终变量之间取得很大的不同。这里的问题是,无论哪种情况,它都是不好的OO。您不应该从对象中请求值并对其进行操作,而应该让对象为您执行操作。

顺便说一句,我绝不是提倡公共变量,而是说设置器和获取器(甚至是属性)太接近已经成为公共变量的位置了。

最大的问题就是,不是OO程序员的人很想使用setter和getter将对象变成传递和操作的属性球(结构),这与面向对象代码的工作原理截然相反。


关于getter设置器,除了这里的其他答案之外,还有一件好事:我可以在其中添加一些验证/转换,因为实际的私有变量假定它是值。
2011年

1
@Kiran是的,但是如果在构造函数中进行设置,则可以进行验证,并且具有不可变的类。对于二传手,我并不是说公共可写变量是好的,我是说二传手也是不好的。对于吸气剂,我可能会认为公共最终变量是可行的选择。
Bill K

在某些语言(例如C#)中,属性与公共变量不二进制兼容,因此,如果将公共变​​量作为API的一部分,但随后要添加一些验证,则在将其转换为财产。最好从一开始就使其成为公共财产。
罗伯特·哈维

@RobertHarvey这仍然意味着您正在公开属性。我的全部观点是,应避免公开属性,并且在必要时应尝试将它们限制在getter范围内,并使类不可变。如果您的班级是不可变的,为什么还要在getter中需要代码-因此,您也可能没有getter。如果您不能编写没有暴露的可变属性的类,那么可以,二传手总是比可写的公共变量好(而且被枪杀总是比被肠子刺中更好)。
Bill K

1
可变状态是可以接受的,但是同样,您应该要求对象为您做某事,而不是对您的对象做某事-因此,当您改变状态时,setter并不是实现该目的的好方法,请尝试使用逻辑编写业务方法在其中。例如,“ move(Up5)而不是setZLocation(getZLocation()+ 5)”。
Bill K

8

吸气剂和吸气剂的使用者进入封装原理。这将使您能够更改类内部的工作方式并保持所有功能正常运行。

例如,如果调用foo.bar了另外3个对象以获取bar的值,而您决定将bar的名称更改为远,则您手上有问题。如果调用了这些对象foo.bar,则必须更改所有具有此功能的类。如果使用了setter / getter,那么您就没有任何更改。另一种可能性是更改变量的类型,在这种情况下,只需将一些转换代码添加到getter / setter中就可以了。


1
+1-成员变量是实现,getter和setter是接口。只有接口应该是公共的。同样,无论是在其基础之上是简单的成员变量还是在其他实现方式上,getter和setter名称都应具有抽象意义。在某些情况下(例外的情况是,由紧密耦合的类构建的子系统是最简单的方法),但无论如何,这只会将隐藏数据的边界推上一个台阶,直到代表整个子系统的类。
2011年

当我需要在班级内部更改工作时,是否可以简单地介绍getter和/或setter方法?在Delphi中很容易做到这一点,但在其他语言中也许不容易吗?
Marjan Venema

@ marjan当然,您可以稍后再介绍getter / setter。然后的问题是,您必须返回并更改所有使用公共字段而不是getter / setter的代码。我不确定我是不是困惑的人还是你。0.o
皮特

3
就我个人而言,我认为这是一个虚假的论点。如果您更改了变量的含义,但是该变量具有getter和setter,则调用什么都没有关系。它不是界面的可见部分。您更可能希望更改获取器和设置器本身的名称,以向客户指示封装变量的含义的更改或说明。在后一种情况下,使用公共变量将比您拥有更好的位置。除了这样的说法,带有getter和setter的变量比封装的更容易暴露。
CB Bailey

1
无论哪种方式,重构操作实际上都是免费的,所以我真的不买这个。另外,除非您绝对需要使用setter或getter,否则这是非常不好的形式—最终,您会得到不了解该概念的人,只是自动为成员添加setter和getter,而不是在需要时添加成员整个代码库中的邪恶。
比尔K

5

使用getter和setter还可让您控制将哪些内容存储在特定变量中。如果内容需要是某种类型或值,则设置程序代码的一部分可以确保新值满足这些要求。如果变量是公共变量,则不能确保满足这些要求。

这种方法还使您的代码更具适应性和可管理性。如果具有适当的功能使该体系结构对所有其他类或使用该类的函数隐藏,则更改类的体系结构会容易得多。如果您具有getter和setter之类的功能,那么已经提到的变量名更改只是许多更改中的一项,而这些更改更容易实现。总体思路是保持尽可能多的私有性,尤其是您的类变量。


非常感谢!我在想你提到的同样情况。如果我想让变量包含在[0,1]中,则应检查其setter中的传入值:)
Oni

当您是唯一一个从事项目工作的开发人员时,很容易认为这样的事情是没有用的,但是当您发现自己与团队一起工作时,您会发现养成这样的技术习惯会很方便,并且可以帮助您避免日后出现许多错误。
肯尼思

如果使用的是C#,请使用属性,如果需要,可以根据属性的设置器部分中的某些逻辑使用属性来设置私有实例。
developerdoug

2

您说:“我认为唯一需要使用getter和setter的情况是,除了set或get之外,还需要执行其他操作。”

如果将来在某个时候您可能需要执行除set和get之外的某些操作,并且您不想在发生这种情况时更改数千行源代码,则应该使用getter和setter 。

如果您不希望有人获取变量的地址并传递给变量,则应使用getter和setter方法,如果该变量可以在没有任何源代码提及的情况下进行更改,或者甚至在对象停止存在后也可以更改,则将带来灾难性的后果。


您的最后一段很好地说明了这两种方法:要求使用getter和setter会阻止其他代码占用某些地址。如果使用外部代码会带来很多好处,而使用外部代码的缺点很少,那么这种限制可能是一件坏事。如果允许这种行为几乎没有好处,并且不利之处很大,那么限制它可能是一件好事。
2015年

1

首先,让我们在范例上弄清楚。

  • 数据结构->可以由适当了解知识的功能遍历和操纵的内存布局。
  • 对象->一个自包含的模块,它隐藏其实现并提供可以通过其通信的接口。

吸气剂/设定剂在哪里有用?

获取器/设置器在数据结构中有用吗?没有。

数据结构是一个内存布局规范,它是一系列功能所共有和操纵的。

通常,任何旧的新功能都可以出现并操纵数据结构,如果这样做可以使其他功能仍然可以理解它,那么该功能将加入家族。否则,它是流氓功能和错误源。

不要误会我的意思,可能会有多个函数系列在数据结构上交织在一起,到处都有弯刀,反光涂层和双代理。当他们每个人都有自己的数据结构可以使用时很好,但是当他们共享它时...想象一下几个犯罪家庭在政治上意见分歧,那么它很快就会变得一团糟。

鉴于混乱的扩展功能系列可以实现,是否有一种方法可以对数据结构进行编码,以使恶意功能不会将所有事情弄乱?是的,它们被称为对象。

获取器/设置器在对象中有用吗?没有。

将数据结构包装在对象中的全部目的是确保不存在恶意功能。如果该功能想加入这个家族,则必须首先对其进行彻底的审查,然后再成为对象的一部分。

获取器和设置器的目的/目的是允许对象外部的功能直接更改对象的内存布局。这听起来像是在允许流氓的门户开放。

边缘案例

有两种情况使公众获得者/制定者有意义。

  • 对象中一部分数据结构由对象管理,但不受对象控制。
  • 一个接口,用于描述数据结构的高级抽象,其中某些元素不应控制于实现对象。

容器和容器接口是这两种情况的完美示例。容器内部管理数据结构(链接列表,地图,树),但由特定人员掌控所有特定元素。接口对此进行了抽象,并完全忽略了实现,仅描述了期望。

不幸的是,许多实现都犯了这个错误,并定义了这类对象的接口以提供对实际对象的直接访问。就像是:

interface Container<T>
{
    typedef ...T... TRef; //<somehow make TRef to be a reference or pointer to the memory location of T
    TRef item(int index); 
}

这已破了。Container的实现必须明确将其内部控制权交给使用它们的任何人。我还没有见过这样的可变值语言(从数据损坏的角度来看,具有不变值语义的语言从定义上来说是很好的,但不一定从数据间谍的角度来看)。

您可以通过仅使用复制语义或使用代理来改进/更正获取器/设置器:

interface Proxy<T>
{
     operator T(); //<returns a copy
     ... operator ->(); //<permits a function call to be forwarded to an element
     Proxy<T> operator=(T); //< permits the specific element to be replaced/assigned by another T.
}

interface Container<T>
{
     Proxy<T> item(int index);
     T item(int index); //<When T is a copy of the original value.
     void item(int index, T new_value); //<where new_value is used to replace the old value
}

可以说,流氓功能仍然可以在这里发挥混乱的作用(只要有足够的努力,大多数事情都是可能的),但是复制语义和/或代理减少了出现许多错误的机会。

  • 溢出
  • 下溢
  • 与sub元素的交互是类型检查/类型检查(在类型丢失的语言中,这是福音)
  • 实际元素可能驻留在内存中,也可能不是。

私人获取者/设定者

这是直接在类型上使用的getter和setter的最后堡垒。实际上,我什至不称这些getter和setter为存取器和操纵器。

在这种情况下,有时总是/几乎总是/一般地操纵数据结构的特定部分需要进行特定的记账。假设更新树的根时,需要清除后备缓存,或者在访问外部数据元素时,需要获取/释放锁。在这些情况下,应用DRY主体并将这些动作组合在一起是有意义的。

在私有上下文中,该家族中的其他功能仍然有可能避开这些“获取和设置”并操纵数据结构。因此,为什么我将它们更多地视为访问者和操纵者。您可以直接访问数据,也可以依靠其他家庭成员来正确处理该部分。

受保护的吸气剂/阻气剂

在受保护的上下文中,它与公共上下文没有很大不同。外国可能的流氓功能想要访问数据结构。所以不,如果它们存在,它们将像公共获取者/制定者一样运作。


0

根据C ++的经验,setter / getter对于以下两种情况很有帮助:

  1. 如果必须在将值分配给成员(如字符串或数组)之前执行健全性检查。
  2. 当需要函数重载时,如-1(或负值)为false时,将int值分配给bool分配时,这将很有帮助。反之亦然。

除此以外,安全性是很重要的一点,但它的重要性仅限于很少的开发应用程序,例如与登录或数据库访问相关的模块。当只有很少的人使用您的模块时,为什么还要打扰编码呢?

By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.