我进入了有关getter和setter方法以及封装的有趣的Internet争论。有人说,他们应该做的只是分配(设置器)或变量访问(获取器),以使它们“纯净”并确保封装。
- 我是对的,这会完全破坏首先使用getter和setter的目的,应该允许进行验证和其他逻辑(当然没有奇怪的副作用)吗?
- 何时应进行验证?
- 设置值时,在设置器内部(以防止对象进入无效状态-我认为)
- 在设置值之前,在设置器之外
- 在对象内部,每次使用该值之前
- 是否允许设置器更改值(也许将有效值转换为某些规范的内部表示形式)?
我进入了有关getter和setter方法以及封装的有趣的Internet争论。有人说,他们应该做的只是分配(设置器)或变量访问(获取器),以使它们“纯净”并确保封装。
Answers:
我记得在大学学习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代码相对整洁。
是否允许设置器更改值(也许将有效值转换为某些规范的内部表示形式)?
在极少数情况下,也许。通常,最好不要这样做。这种事情最好留给另一种方法。
如果getter / setter只是简单地反映了值,那么拥有它们或将值设为私有毫无意义。如果您有充分的理由,那么将某些成员变量公开是没有错的。如果您正在编写3d点类,那么公开使用.x,.y和.z是很有意义的。
正如拉尔夫·沃尔多·爱默生(Ralph Waldo Emerson)所说:“愚蠢的一致性是小思想家的妖怪,受到小政治家,哲学家和Java设计师的崇拜。”
在有副作用的地方,需要更新其他内部变量,重新计算缓存的值并保护类免受无效输入的影响,getter / setter很有用。
对于他们来说,通常的理由是隐藏内部结构,通常是最没有用的。例如。我将这些点存储为3个浮点数,我可能会决定将它们作为字符串存储在远程数据库中,因此我将使getters / setters隐藏它们,就像您可以这样做那样对调用者的代码没有任何其他影响。
IEnumerable<T>
要比将其强制为类似要好得多List<T>
。我要说的您的数据库访问示例违反了单一职责-将模型的表示与模型的持久性混合在一起。
通过Eiffel语言引入的一种通用的类设计策略是Command-Query Separation。这个想法是,一个方法应该要么告诉你一些有关的对象或通知对象做一些事情,但不能两者都做。
这仅与类的公共接口有关,而与内部表示无关。考虑一个由数据库中的行支持的数据模型对象。您可以在不加载数据的情况下创建对象,然后在第一次调用getter时实际上执行SELECT
。很好,您可能正在更改有关对象表示方式的一些内部详细信息,但没有更改它在该对象的客户端上的显示方式。您应该能够多次调用getter并仍然获得相同的结果,即使它们做不同的工作来返回这些结果。
同样,setter在合同上看起来像只是在改变对象的状态。它可能以某种令人费解的方式做到这一点- UPDATE
向数据库写入数据,或将参数传递给某些内部对象。很好,但是做一些与设置状态无关的事情会令人惊讶。
Meyer(埃菲尔铁塔的创建者)也对验证有话要说。本质上,每当对象处于静止状态时,它都应处于有效状态。因此,在构造函数完成之后,每个外部方法调用之前和之后(但不一定是在此之后),对象的状态应该保持一致。
有趣的是,在该语言中,用于调用过程和读取公开属性的语法都相同。换句话说,调用者无法分辨他们是使用某种方法还是直接使用实例变量。只是在不会隐藏实现细节的语言中,甚至出现问题的地方-如果调用者不知道,则可以在公共ivar和访问者之间切换,而不会将更改泄漏给客户端代码。
我倾向于认为,setter和getter是邪恶的,只应在由框架/容器管理的类中使用。适当的类设计不应产生吸气剂和吸气剂。
编辑:关于这个主题的写得很好的文章。
Edit2:在OOP方法中,公共字段是胡说八道;说吸气器和塞特器是邪恶的,我并不是说应该用公共场所代替它们。