在getter和setter里面应该允许什么?


45

我进入了有关getter和setter方法以及封装的有趣的Internet争论。有人说,他们应该做的只是分配(设置器)或变量访问(获取器),以使它们“纯净”并确保封装。

  • 我是对的,这会完全破坏首先使用getter和setter的目的,应该允许进行验证和其他逻辑(当然没有奇怪的副作用)吗?
  • 何时应进行验证?
    • 设置值时,在设置器内部(以防止对象进入无效状态-我认为)
    • 在设置值之前,在设置器之外
    • 在对象内部,每次使用该值之前
  • 是否允许设置器更改值(也许将有效值转换为某些规范的内部表示形式)?

18
关于getter和setter的最好的事情是不拥有它们的能力,即离开setter并拥有一个只读属性,离开getter并拥有当前值与任何人无关的config选项。
Michael Borgwardt 2012年

7
@MichaelBorgwardt请将两者都留下来,以保持干净的“告诉,不要问”界面。属性=潜在的代码气味。
康拉德·鲁道夫2012年


2
二传手也可以用来引发一个事件
John Isaiah Carmona 2012年

@KonradRudolph我同意您的说法,尽管我想特别强调“潜力”一词。
Phil

Answers:


37

我记得在大学学习C ++时与讲师有过类似的争论。当我可以将变量公开时,我只是看不到使用getter和setter的意义。现在,凭借多年的经验,我了解得更多,并且我已经学会了比简单地说“保持封装”更好的理由。

通过定义getter和setter,您将提供一个一致的接口,这样,如果您希望更改实现,就不太可能破坏依赖的代码。当您的类通过API公开并在其他应用或第三方中使用时,这一点尤其重要。那么,放入吸气剂或装夹器中的东西呢?

通常,将getter更好地实现为用于访问值的简单的向下传递,因为这可以使它们的行为可预测。我之所以说是一般性的,是因为我已经看到了使用吸气剂来访问通过计算甚至通过条件代码操纵的值的情况。如果要创建在设计时使用的可视组件,通常效果不佳,但是在运行时似乎很方便。但是,此方法与使用简单方法之间没有真正的区别,除了使用方法时,通常更可能更恰当地命名方法,以便在阅读代码时“ getter”的功能更加明显。

比较以下内容:

int aValue = MyClass.Value;

int aValue = MyClass.CalculateValue();

第二个选项清楚地表明正在计算该值,而第一个示例告诉您,您只是在返回一个值而并不了解该值本身。

您可能会争辩说以下内容会更清楚:

int aValue = MyClass.CalculatedValue;

但是,问题在于您假设该值已在其他位置被操纵。因此,在使用吸气剂的情况下,尽管您可能希望假设在返回值时可能正在发生其他事情,但是很难在属性的上下文中使此类事情变得清晰,并且属性名称中绝不能包含动词否则,一眼就很难理解所使用的名称在访问时是否应该用括号修饰。

二传手的情况略有不同。设置程序提供一些额外的处理以验证提交给属性的数据是完全适当的,如果设置值会违反属性的定义边界,则抛出异常。但是,某些开发人员在向设置器添加处理方面遇到的问题是,总是有诱惑力让设置器做更多的事情,例如以某种方式执行数据的计算或操作。在这里,您可能会得到一些在某些情况下无法预测或不希望出现的副作用。

对于设置员,我总是采用简单的经验法则,即对数据做的尽可能少。例如,我通常会允许边界测试和四舍五入,以便我都可以在适当情况下引发异常,或者避免在可以避免的情况下避免不必要的异常。浮点属性是一个很好的示例,您可能希望舍入过多的小数位以避免引发异常,同时仍然允许在范围值中输入一些其他的小数位。

如果对setter输入进行某种操作,则与getter会遇到相同的问题,即很难让其他人仅通过命名来知道setter在做什么。例如:

MyClass.Value = 12345;

这是否可以告诉您将值提供给设置器后会发生什么变化?

怎么样:

MyClass.RoundValueToNearestThousand(12345);

第二个示例准确地告诉您数据将要发生什么,而第一个示例则不会让您知道您的值是否将被任意修改。在阅读代码时,第二个示例的目的和功能将更加清晰。

我是对的,这会完全破坏首先使用getter和setter的目的,应该允许进行验证和其他逻辑(当然没有奇怪的副作用)吗?

具有“ getter”和“ setters”并不是为了“纯净”而进行封装,而是为了使代码易于重构而无需冒险更改类接口的封装,否则会破坏类与调用代码的兼容性。验证完全适合于setter,但是,如果调用代码依赖于以特定方式发生的验证,则验证的更改可能会破坏与调用代码的兼容性,这是一个很小的风险。这是一种通常很少见且风险相对较低的情况,但为完整起见,应予以注意。

何时应进行验证?

验证应在实际设置值之前在设置器的上下文中进行。这样可以确保如果引发异常,则对象的状态不会更改,并且可能会使其数据无效。我通常发现最好将验证委托给单独的方法,这将是setter中首先要调用的方法,以使setter代码相对整洁。

是否允许设置器更改值(也许将有效值转换为某些规范的内部表示形式)?

在极少数情况下,也许。通常,最好不要这样做。这种事情最好留给另一种方法。


回复:更改值,如果将设置程序传递给非法值,则将其设置为-1或某些NULL标志令牌可能是合理的
Martin Beckett 2012年

1
这有两个问题。任意设置值会产生蓄意且不清楚的副作用。而且,它不允许调用代码接收可用于更好地处理非法数据的反馈。这对于UI级别的值尤为重要。为了公平起见,我刚才想到的一个例外可能是,如果允许以标准日期/时间格式存储日期,同时允许多种日期格式作为输入。有人可能会争辩说,只要输入数据合法,这种特殊的“副作用”就是验证时对数据的规范化。
S.Robins 2012年

是的,setter的理由之一是,如果可以设置该值,则应返回true / false。
马丁·贝克特

1
“浮点属性是一个很好的示例,您可能希望舍入过多的小数位以避免引发异常”在什么情况下,小数位数太多会引发异常?
Mark Byers

2
我看到将值更改为规范表示形式是一种验证形式。默认缺少的值(例如,如果时间戳仅包含时间,则为当前日期)或缩放值(.93275-> 93.28%)以确保内部一致性应该可以,但是任何此类操作应在API文档中明确调用,尤其是在API中不常见的习惯用法时。
TMN 2012年

20

如果getter / setter只是简单地反映了值,那么拥有它们或将值设为私有毫无意义。如果您有充分的理由,那么将某些成员变量公开是没有错的。如果您正在编写3d点类,那么公开使用.x,.y和.z是很有意义的。

正如拉尔夫·沃尔多·爱默生(Ralph Waldo Emerson)所说:“愚蠢的一致性是小思想家的妖怪,受到小政治家,哲学家和Java设计师的崇拜。”

在有副作用的地方,需要更新其他内部变量,重新计算缓存的值并保护类免受无效输入的影响,getter / setter很有用。

对于他们来说,通常的理由是隐藏内部结构,通常是最没有用的。例如。我将这些点存储为3个浮点数,我可能会决定将它们作为字符串存储在远程数据库中,因此我将使getters / setters隐藏它们,就像您可以这样做那样对调用者的代码没有任何其他影响。


1
哇,Java的设计师是神圣的吗?</ jk>

@delnan-显然不是-否则他们会押韵;-)
Martin Beckett

创建其中x,y,z均为Float.NAN的3d点是否有效?
Andrew T Finnell,2012年

4
我不同意您的观点,即“通常的理由”(隐藏内部结构)是吸气剂和吸气剂最不有用的特性。在C#中,将属性设置为可以更改其基础结构的接口绝对有用-例如,如果您只希望将属性用于枚举,那么说起来IEnumerable<T>要比将其强制为类似要好得多List<T>。我要说的您的数据库访问示例违反了单一职责-将模型的表示与模型的持久性混合在一起。
布兰登·林顿

@AndrewFinnell-这可能是将点标记为不可能/无效/删除等的好方法
Martin Beckett

8

迈耶的统一访问原则:“模块提供的所有服务都应通过统一的符号表示,无论是通过存储还是通过计算实现,都不得背叛。” 是吸气剂/设置剂(即“属性”)背后的主要原因。

如果您决定缓存或延迟计算类的一个字段,那么只要您只公开属性访问器而没有具体数据,则可以随时更改此字段。

在我看来,值对象,简单结构不需要这种抽象,而成熟的类则需要。


2

通过Eiffel语言引入的一种通用的类设计策略是Command-Query Separation。这个想法是,一个方法应该要么告诉你一些有关的对象通知对象做一些事情,但不能两者都做。

这仅与类的公共接口有关,而与内部表示无关。考虑一个由数据库中的行支持的数据模型对象。您可以在不加载数据的情况下创建对象,然后在第一次调用getter时实际上执行SELECT。很好,您可能正在更改有关对象表示方式的一些内部详细信息,但没有更改它在该对象的客户端上的显示方式。您应该能够多次调用getter并仍然获得相同的结果,即使它们做不同的工作来返回这些结果。

同样,setter在合同上看起来像只是在改变对象的状态。它可能以某种令人费解的方式做到这一点- UPDATE向数据库写入数据,或将参数传递给某些内部对象。很好,但是做一些与设置状态无关的事情会令人惊讶。

Meyer(埃菲尔铁塔的创建者)也对验证有话要说。本质上,每当对象处于静止状态时,它都应处于有效状态。因此,在构造函数完成之后,每个外部方法调用之前和之后(但不一定是在此之后),对象的状态应该保持一致。

有趣的是,在该语言中,用于调用过程和读取公开属性的语法都相同。换句话说,调用者无法分辨他们是使用某种方法还是直接使用实例变量。只是在不会隐藏实现细节的语言中,甚至出现问题的地方-如果调用者不知道,则可以在公共ivar和访问者之间切换,而不会将更改泄漏给客户端代码。


1

另一个可以接受的事情是克隆。在某些情况下,您需要确保在有人给您上课后,例如。列表,他不能在您的班级内更改它(也不能更改该列表中的对象)。因此,您可以在setter中复制参数的深层副本,并在getter中返回深层副本。(将不可变类型用作参数是另一种选择,但以上假设是不可能的),但是如果不需要,请不要在访问器中进行克隆。将属性/获取者和设置者视为恒定成本操作很容易(但不恰当),因此这是等待api用户使用的性能隐患。


1

属性访问器和存储数据的ivars之间并不总是存在1-1映射。

例如,center即使没有存储视图中心的ivar,视图类也可能提供属性。设置center会导致其他ivars的更改,例如origintransform,等等,但是该类的客户不知道或不在乎如何 center存储(只要它能正常工作)。但是,应该发生的是,设置center会导致发生超出保存新值所需的一切的事情,但是已完成。


0

关于setter和getter的最好之处在于,它们可以轻松更改API的规则而无需更改API。如果检测到错误,则很有可能可以修复库中的错误,而不必让每个使用者都更新其代码库。


-4

我倾向于认为,setter和getter是邪恶的,只应在由框架/容器管理的类中使用。适当的类设计不应产生吸气剂和吸气剂。

编辑:关于这个主题的写得很好的文章

Edit2:在OOP方法中,公共字段是胡说八道;说吸气器和塞特器是邪恶的,我并不是说应该用公共场所代替它们。


1
我认为您遗漏了本文的要点,该文章建议您谨慎使用getters / setter方法,并除非必要,否则避免公开类数据。此恕我直言是明智的设计原则,应由开发人员故意应用。就在类上创建属性而言,您可以简单地公开一个变量,但这使得在设置变量时难以提供验证,或者在“获取”时很难从备用源获取值,这实质上违反了封装,并且可能将您锁定在难以维护的界面设计中。
罗宾斯(S.Robins)2012年

@ S.Robins在我看来,公众获取者和设定者是错误的;公共领域是错误的(如果可以比较的话)。
m3th0dman 2012年

@methodman是的,我同意公共领域是错误的,但是公共财产可能有用。该属性可用于提供验证或与数据设置或返回有关的事件的场所,具体取决于当时的特定要求。本质上,getter和setter本身并没有错。另一方面,根据情况,在设计和可维护性方面,如何以及何时使用或滥用它们将被视为较差。:)
S.Robins 2012年

@ S.Robins考虑OOP和建模的基础;对象应该复制真实的实体。是否存在具有这种类型的操作/属性的现实生活中的实体,例如吸气剂/设定器?
m3th0dman 2012年

为避免在此处发表过多评论,我将在本次聊天中加入此对话,并在其中讨论您的评论。
S.Robins,2012年
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.