允许吸气剂的确切问题是什么?


15

不是在寻找有关语义的意见,而只是在明智地使用吸气剂是一个实际障碍的情况下。也许这使我陷入了对它们的永无止境的螺旋式旋转,也许替代方法是更清洁并自动处理吸气剂,等等。

我听过所有论据,听过它们很不好,因为它们迫使您将对象视为数据源,它们违反了对象的“纯状态”,即“不要给出太多,但要做好准备”接受很多”。

但是,绝对没有明智的理由说明a为何getData是一件坏事,实际上,一些人认为这与语义有关,吸气剂本身就很好,但只是不要给它们起名字getX,对我来说,这至少很有趣。

如果我明智地使用吸气剂,并且没有明显的数据(如果将其推出,则对象的完整性不会破坏),那么没有意见的一件事是什么

当然,允许使用getter来加密用于加密某些内容的字符串是很愚蠢的,但是我说的是系统需要运行的数据。也许您的数据是Provider从对象中拉出的,但是,仍然,对象仍然需要允许该对象Provider执行a $provider[$object]->getData,因此无法解决。


我为什么要问:对我来说,合理使用吸气剂并在被视为“安全”数据上使用吸气剂时,我有99%的吸气剂用于识别对象,例如,我通过代码询问Object, what is your name? Object, what is your identifier?,任何使用对象的人都应该了解有关对象的这些知识,因为关于编程的几乎所有内容都是身份,还有谁比对象本身更了解对象是什么?因此,除非您是纯粹主义者,否则我看不到任何实际问题。

我已经看过所有关于“为什么吸气剂/塞特器”很糟糕的StackOverflow问题,尽管我同意在99%的情况下塞特斯确实很不好,但是不必因为吸韵而对吸气剂一视同仁。

设置器会损害对象的身份,并且很难调试谁在更改数据,但是获取器却无能为力。


2
我的建议是忘记了大多数面向对象的理论。实践。编写很多代码,然后对其进行维护。您将学到很多东西,然后从几个月后的实际工作中学习到更多有用的内容和无效的内容。
jpmc26

1
@ jpmc26 ....的母亲……我从来没有真正意识到我们没有一个人真正正确地进行OOP,我一直都有严格与对象字段相关的封装概念,但是对象本身是一个状态,并且正在被自由共享。的确,每个人都在错误地进行OOP操作,否则,如果范式是对象,则不可能进行OOP操作。我严格使用OOP来表达系统的工作方式,并且从未将其视为圣杯或SOLID。它们只是我主要用来定义(通过实施)合理解释我的概念的工具。如果这是正确的任何读物?
coolpasta

2
--cont,我写的越多,我越意识到编程是关于能够尽可能多地抽象化以便能够对他人以及对您进行代码库推理的全部内容。当然,技术方面是要确保您进行了正确的检查/设置了正确的规则。实际上,无论谁设法使其他人更好地理解自己的代码,都是赢家。确保对阅读它的任何人都有意义,然后通过使用接口正确地对对象进行分段,检查并在更容易推理时切换到其他范式来确保它不会失败:保持灵活性。
coolpasta

2
在我看来,这就像个稻草人。我不相信任何认真地编写可用代码的认真的开发人员都会认为明智地使用getters是一个问题。这个问题意味着吸气剂可以被合理地使用,这进一步暗示了吸气剂可以是明智的。您是否正在寻找可以说没有合理使用吸气剂的人?您将等一会儿。
JimmyJames

2
@coolpasta你是对的。我真的不在乎它是否是纯OOP。更好。给我一个在人类可以理解的破损代码与无法理解的,完美无缺的CPU喜爱代码之间的选择,并且每次都会取走人类友好的代码。当OOP减少了我一次必须考虑的事情数量时,它仅对我有用。这是我喜欢简单的过程的唯一原因。没有它,您就是在无缘无故地让我跳过多个源代码文件。
candied_orange

Answers:


22

没有吸气剂,您就无法编写好的代码。

原因不是因为吸气剂不会破坏封装,而是这样做。这并不是因为吸气剂不会诱使人们不去遵循OOP,否则OOP会让他们将所处理的数据放入方法中。他们是这样。不,由于边界,您需要吸气剂。

当您遇到无法移动方法并迫使您移动数据的边界时,封装和保留方法以及它们所作用的数据的想法根本行不通。

真的就是这么简单。如果在没有边界的情况下使用吸气剂,则最终将没有真实对象。一切开始趋于程序化。哪个工作得比以往更好。

真正的OOP并不是随处可见的。它仅在这些边界内起作用。

那些边界并不可怕。他们里面有代码。该代码不能是OOP。它也不起作用。没有任何代码会破坏我们的理想,因此它可以应对严峻的现实。

迈克尔·费特斯(Michael Fetters)用白色的结缔组织将橙子的各个部分连在一起后,称其为筋膜编码。

这是一种思考的绝妙方法。它解释了为什么可以在同一代码库中同时包含两种代码。没有这种观点,许多新程序员会坚守自己的理想,然后心碎,在达到自己的第一个界限时就放弃这些理想。

理想只在适当的地方起作用。不要仅仅因为它们在任何地方都不工作而放弃它们。在他们工作的地方使用它们。那个地方是面板保护的多汁部分。

边界的一个简单示例是集合。这拥有某些东西,却不知道它是什么。当集合设计者不知道将要持有什么对象时,他们如何将其行为功能转移到集合中?你不能 您有一个界限。这就是为什么集合有吸气剂的原因。

现在,如果您知道,则可以移动该行为,并避免移动状态。当您知道时,您应该。您只是不总是知道。

有人称这是务实的。是的。但是很高兴知道我们为什么要务实。


您已经表示不想听语义上的争论,而似乎是在提倡“明智的获取者”。您正在要求挑战这个想法。我想我可以证明您的构想方式存在问题。但是它也认为我知道你来自哪里,因为我去过那里。

如果您想在任何地方使用吸气剂,请查看Python。没有私人关键字。但是Python确实可以OOP。怎么样?他们使用语义技巧。他们用下划线标出了任何私人的名称。只要您对此负责,您甚至可以阅读它。他们经常说:“我们都是成年人。”

那么,将吸气剂放在Java或C#的所有内容之间有什么区别?抱歉,这是语义。Python下划线约定清楚地向您发出信号,表明您正在四处寻找员工的唯一门。对所有事物都使用吸气剂,您就会松开该信号。通过反射,您仍然可以剥离私有,但仍然不会丢失语义信号。这里根本没有结构性论点。

因此,剩下的工作就是决定在哪里悬挂“仅限员工”标志。什么应该被视为私人?您称其为“明智的吸气剂”。正如我已经说过的,获取吸气剂的最佳理由是迫使我们背离理想的界限。那不应该导致所有事情的失败。当确实导致吸气时,您应该考虑将行为进一步转移到可以保护它的多汁位中。

这种分离产生了一些条件。数据传输对象或DTO,不保留任何行为。唯一的方法是getters,有时是setter,有时是构造函数。这个名字很不幸,因为它根本不是一个真正的对象。getter和setter实际上只是调试代码,可为您提供一个设置断点的地方。如果不是那样的话,那将是一堆公共场所。在C ++中,我们曾经称它们为结构。他们与C ++类的唯一区别是它们默认为public。

DTO很不错,因为您可以将它们放在边界墙上,并将其他方法安全地保存在一个多汁的行为对象中。一个真实的对象。没有吸气剂违反它的封装。我的行为对象可以通过将它们用作参数对象来吃掉DTO 。有时我必须制作一份防御性副本以防止共享的可变状态。我不会在边界内多汁的部分内散布可变的DTO。我封装它们。我藏起来了 当我终于遇到一个新的边界时,我会旋转一个新的DTO并将其扔在墙上,从而使它成为其他人的问题。

但是,您想提供表达身份的吸气剂。恭喜,您发现了边界。实体的身份超出其参考范围。也就是说,超出了他们的内存地址。因此,它必须存储在某个地方。而且某些东西必须能够通过其身份来引用这个东西。表达身份的吸气剂是完全合理的。一堆使用该吸气剂来做出实体可能自己做出的决定的代码不是。

最后,错误的不是吸气剂的存在。它们远胜于公共领域。不好的是,当您习惯使用它们假装您是面向对象的时候。吸气剂很好。面向对象是好的。吸气剂不是面向对象的。使用吸气剂开辟出一个面向对象的安全场所。


这正是我问的原因,我不确定与Robert的对话是否可见,我还试图找出一个具体案例,显然使用“明智的吸气剂”会伤害我。我,我自己不同意与制定者,因为至少我自己,这是非常难的属性所有权这样一个动作,我用的一切使用setter和的时候,我意识到,即使是1次传,当受到太多的对象杀死我...我不得不重新编写所有内容。经过长时间的详尽测试,合理地使用吸气剂,如果您不是纯粹主义者,则不会有任何不利方面。
coolpasta

1
@coolpasta,您可以创建数据所有者对象。数据所有者对象是数据对象,但是它们经过设计和命名,以使它们(显然)拥有数据。当然,这些对象将具有吸气剂。
rwong

1
@rwong是什么意思clearly?如果数据是通过某种传递给我的,那么很显然,我的对象现在拥有该数据了,但是根据语义,它现在还没有,只是从别人那里得到了数据。然后,它必须执行操作来转换该数据,使其成为自己的数据,一旦触摸到该数据,就必须进行语义更改:您获得了名为的数据$data_from_request,现在就对其进行了操作?命名它$changed_data。您希望将此数据公开给其他人吗?创建一个getter getChangedRequestData,然后明确设置所有权。还是?
coolpasta

1
“如果没有吸气剂,就无法编写出良好的代码。” 这是完全错误的,因为边界固有地需要吸气剂。这不是实用主义,只是懒惰。这是一个相当容易一点,就把数据翻过栅栏,并用它来完成,有关使用情况的思考和设计一个好的行为API 困难的。
罗伯特·布鲁克尼(RobertBräutigam),

2
很好的答案!
cmaster-恢复莫妮卡

10

吸气剂违反好莱坞原则(“不要打电话给我们,我们会打电话给您”)

好莱坞原则(又称控制反转)规定,您无需调用库代码即可完成工作;框架会调用您的代码。因为框架控制着事物,所以没有必要向其客户广播其内部状态。您不需要知道。

在最隐蔽的形式下,违反好莱坞原则意味着您正在使用吸气剂获取有关类状态的信息,然后根据所获取的值来决定调用该类的方法。它最好地违反了封装。

使用吸气剂意味着您实际上不需要该值

您可能实际上需要提高性能

在必须具有尽可能高的性能的轻量级对象的极端情况下,有可能(尽管极不可能)无法支付getter施加的很小的性能损失。99.9%的时间不会发生。


1
我明白了,我会信守你的真相,这是我不需要知道的。。但是我已经到达需要的地方。我有一个Generator遍历我所有Items对象的对象,然后getName从每个对象中调用Item以做进一步的处理。这是什么问题?然后,作为回报,Generator吐出格式化的字符串。这是在我的框架之内,然后我有一个API,使人们可以用来运行用户提供的任何内容,而无需接触框架。
coolpasta

3
What is the issue with this?我看不到。本质上就是map函数的功能。但这不是你问的问题。您本质上是在问:“在任何情况下都不宜使用吸气剂。” 我回答了两个,但这并不意味着您完全放弃了二传手。
罗伯特·哈维,

1
您是在问错误的人这个问题。我是一个实用主义者。我会做最适合我的特定程序的事情,除非“原则”能达到我的目的,否则不要投入太多。
罗伯特·哈维

2
@ jpmc26:框架。 不是图书馆。
罗伯特·哈维

2
框架是一种试图将通用语言转换为特定领域的语言的尝试。库是可重用算法的集合。任一人都将要求您依靠它。是否要硬编码依赖关系或使依赖关系易于替换取决于您。
candied_orange

2

Getters泄漏实现细节和中断抽象

考虑 public int millisecondsSince1970()

您的客户会认为“哦,这是一个整数”,并且假设它是一个整数,将会进行数以千计的调用,对日期进行整数运算等。当您意识到需要整数时,将会有很多有问题的旧代码。在Java世界中,您将添加@deprecated到API,每个人都将忽略它,并且您将不得不维护过时的错误代码。:-((我假设其他语言是相似的!)

在这种情况下,我不确定哪种更好的选择,@ candiedorange的答案就很对了,很多时候需要使用吸气剂,但是这个例子说明了缺点。每个公共获取者都倾向于将您“锁定”在某个实现中。如果您确实需要“跨越边界”,请使用它们,但请尽量少使用,并且要谨慎和有远见。


的确如此,但是将数据类型/结构强制应用于对象的变量以及将getter用于该变量的任何方法的模式解决方案是什么?
coolpasta

1
这个例子说明了一种不同的现象,原始的迷恋。如今,大多数库和框架都具有分别表示timepoint和的类timespan。(epoch是的特定值timepoint。)在这些类上,它们提供诸如getIntgetLong或的吸气剂getDouble。如果编写了操纵时间的类,以便它们(1)将与时间相关的算法委托给这些对象和方法,并且(2)通过getter提供对这些时间对象的访问,那么就可以解决问题,而无需摆脱getters 。
rwong

1
总而言之,吸气剂是API的一部分,因此在设计吸气剂时应充分考虑所有后果,包括过去,现在和将来的后果。但这并不能得出避免或减少吸气剂数量的结论。实际上,如果将来需要访问一条特定的信息,而省略了一个getter方法,那将需要更改库端API和更改代码。因此,是否应该实施或避免特定吸气剂的决定应基于领域和意图,而不是基于预防性原因。
rwong

1
“ millisecondsSince1970”在1970年1月底之前停止了32位int的工作,因此我怀疑会使用很多代码:-)
gnasher729

1
@rwong有点正确,但是您假设代表时间点的首选库是稳定的。他们不是。例如:moment.js
user949300,19年

1

我认为首要要记住的是绝对值总是错误的。

考虑一个地址簿程序,该程序仅存储人们的联系信息。但是,让我们做对并将其分解为控制器/服务/存储库层。

还有的将是一个Person保存实际数据类:姓名,地址,电话号码等。AddressPhone是类也一样,所以我们可以在以后使用多态支持非美方案。这三个类都是Data Transfer Objects,因此它们将只不过是getter和setter。而且,这是有道理的:除了覆盖equals和之外,它们将没有任何逻辑getHashcode。要求他们给您一个完整的人的信息表示形式是没有意义的:是否用于HTML演示,自定义GUI应用程序,控制台应用程序,HTTP端点等?

这就是控制器,服务和存储库的所在。由于依赖注入,所有这些类将变得逻辑繁重且轻巧。

控制器将注入一个服务,该服务将公开类似get和的方法save,这两个方法都将get带有一些合理的参数(例如,可能具有覆盖名称的搜索,邮政编码的搜索或仅获取所有内容的覆盖;save可能会采用单Person并保存)。控制器将具有哪些吸气剂?能够获取具体类的名称可能对日志记录很有用,但是除了注入的状态之外,它将保持什么状态?

同样,服务层将注入一个存储库,因此它实际上可以获取并保留Person,并且可能具有一些“业务逻辑”(例如,也许我们将要求所有Person对象至少具有一个地址或电话)数)。但是,再说一遍:除了注入的对象之外,该对象还处于什么状态?再次,没有。

在存储库层,事情变得更加有趣:它将与存储位置建立连接。文件,SQL数据库或内存中的存储。在这里,添加一个getter来获取文件名或SQL连接字符串是很诱人的,但这是被注入到类中的状态。存储库是否应该有一个吸气剂来告知它是否已成功连接到其数据存储?好吧,也许吧,但是信息库类本身之外的信息有什么用?如果需要,存储库类本身应该能够尝试重新连接到其数据存储,这表明该isConnected属性已经具有可疑的价值。此外,某个isConnected属性可能恰好在最需要正确的时候就会出错:检查isConnected在尝试获取/存储数据之前,并不能保证在进行“真实”调用时仍会连接存储库,因此它并不会消除对异常处理的需求(在某些地方存在一些参数)这个问题的范围)。

还考虑单元测试(您正在编写单元测试,对吗?):服务不会期望注入特定的类,而是期望注入接口的具体实现。这样一来,应用程序就可以整体上更改数据的存储位置,而无需执行任何操作,而只需换出注入到服务中的存储库即可。它还允许通过注入模拟存储库对该服务进行单元测试。这意味着要根据接口而不是类进行思考。仓库接口会暴露任何吸气剂吗?也就是说,是否存在任何内部状态:对于所有存储库都是公共的,在存储库外部有用,而不是注入到存储库中?我很难想起任何我自己。

TL; DR

总结:除了那些仅用于携带数据的类外,堆栈中没有其他地方可以放置吸气剂:状态是在工人阶级之外注入的,或者与之无关。


我发现这种思路与关于正确使用C#属性的辩论非常相似。简而言之,期望属性(可以是仅使用getter或使用getter-setter的对)要遵守一定的合同,例如“您所设置的就是所获得的东西”,“ getter应该基本上没有副作用”, “ getter应该避免引发错误”等。与您的结论相反,有许多对象状态有利于公开为属性(或getter)。这里有太多例子无法引用。
rwong

2
@rwong我想知道一些示例,比如说一个团队实施的“普通”“企业”应用程序。为了简单起见,我们还假设不涉及第三方。您知道,一个独立的系统,例如日历,宠物店,随您便。
罗伯特·布劳蒂甘(RobertBräutigam),

1

如果我明智地使用吸气剂,并且没有明显的数据(如果将其推出,则对象的完整性不会破坏),那么没有意见的一件事是什么?

可变成员。

如果通过getter公开的对象中包含事物的集合,则可能会使自己暴露于与添加到集合中的错误事物相关的错误。

如果您有一个通过getter公开的可变对象,则可能会以您意想不到的方式使自己适应正在更改的对象的内部状态。

一个非常糟糕的例子是一个对象,该对象从1到100计数,暴露了其Current值作为可变参考。这允许第三方对象更改Current的值,以使该值可能超出预期范围。

这主要是暴露可变对象的问题。公开结构或不可变对象根本不是问题,因为对结构或不可变对象的任何更改都将更改副本(通常在结构的情况下通过隐式副本,在不可变对象的情况下通过显式副本)。

使用比喻可能会更容易看到差异。

一朵花有许多与众不同的事物。它有一个Color,并且有许多花瓣(NumPetals)。如果我观察那朵花,在现实世界中,我可以清楚地看到它的颜色。然后,我可以根据该颜色做出决定。例如,如果颜色为黑色,则不给女友,但如果颜色为红色,则给女友。在我们的对象模型中,花朵的颜色将作为吸气剂暴露在花朵对象上。我对这种颜色的观察对于我将要执行的动作很重要,但它完全不会影响花朵对象。我应该不能改变那朵花的颜色。同样,隐藏color属性没有任何意义。花通常不能阻止人们观察其颜色。

如果我将花朵染色,则应在花朵上调用ReactToDye(Color dyeColor)方法,该方法将根据花朵的内部规则对其进行更改。然后,我可以再次查询该Color属性,并在调用ReactToDye方法之后对其中的任何更改做出反应。我直接修改Color花朵的,这对我是错误的,如果可以的话,抽象就被破坏了。

有时(不太频繁,但仍然足够频繁,值得一提)二传手是非常有效的OO设计。如果我有一个Customer对象,则公开其地址的setter是非常有效的。您是否称呼它setAddress(string address)还是ChangeMyAddressBecauseIMoved(string newAddress)string Address { get; set; }是语义问题。该对象的内部状态需要更改,并且执行此操作的适当方法是设置该对象的内部状态。即使我需要提供住所地址的历史记录Customer,也可以使用设置器来适当更改我的内部状态。在这种情况下,Customer将保持不变是没有意义的,没有比提供设置器更好的方法来更改地址。

我不确定谁在暗示Getter和Setter是不好的,或者破坏了面向对象的范例,但是如果这样做,他们可能是针对他们碰巧使用的语言的特定语言功能而这样做的。我感到这种感觉源于Java的“一切都是对象”文化(并且有可能将其扩展到其他语言)。.NET世界根本没有这个讨论。Getter和Setters是一流的语言功能,不仅由使用该语言编写应用程序的人使用,而且由语言API本身使用。

使用吸气剂时应小心。您既不想公开错误的数据,也不想以错误的方式公开数据(即,可变对象,对可以允许使用者修改内部状态的结构的引用)。但是您确实希望对对象进行建模,以便以某种方式封装数据,以便外部使用者可以使用您的对象,并防止这些使用者滥用这些对象。通常,这将需要吸气剂。

总结一下:Getter和Setter是有效的面向对象设计工具。它们的用法不应过于宽泛,以免暴露每个实现细节,但您也不要如此谨慎地使用它们,以使对象的使用者无法有效地使用该对象。


-1

一件事,没有意见,如果我使用吸气剂就会破裂...

可维护性将被破坏。

任何时候(我应该几乎总是说),您提供的吸气剂基本上比您要求的要多。这也意味着您必须维护超出要求的范围,因此您无缘无故降低了可维护性。

让我们来看@candied_orange的Collection示例。与他写的相反,Collection 不应该有吸气剂。存在集合是出于非常特定的原因,尤其是要遍历所有项目。此功能对任何人都不是一个惊喜,因此在中实现Collection,而不是使用for-cycles和getters或其他方法将这种显而易见的用例推送给用户。

公平地说,某些界限确实需要吸气剂。如果您真的不知道它们将用作什么,则会发生这种情况。例如,您现在可以以编程方式从Java的Exception类中获取stacktrace ,因为人们出于种种奇怪的原因(因为作者没有或无法设想)使用它。


1
反例。考虑一个需要Collection同时迭代两个s 的函数,如中所述(A1, B1), (A2, B2), ...。这需要“ zip”的实现。如果在两个功能之一上实现了迭代功能,则如何实现“ zip” Collection-如何在另一个功能中访问相应项?
rwong

@rwong Collection必须zip自行实现,具体取决于库的编写方式。如果没有,请实现它,然后向库创建者提交拉取请求。请注意,我同意有时某些边界确实需要吸气剂,但我真正的问题是吸气剂如今无处不在。
罗伯特·布鲁克尼(RobertBräutigam),

在这种情况下,这意味着库必须在Collection对象上具有吸气剂,至少对于其自己的实现zip。(也就是说,getter必须至少具有程序包可见性。)现在,您依靠库创建者接受您的请求请求...
rwong

我不太了解你 该Collection显然可以访问自己的内部状态,或者更精确地说任何Collection情况下都可以访问任何其他的内部状态。它是同一类型,不需要吸气剂。
罗伯特·布鲁克尼(RobertBräutigam),

我也听到了您的声音,但是再说一次,对吸气剂更好的解决方案到底是什么?我知道这超出了问题的范围。
coolpasta
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.