51

艾伦·霍鲁布(Allen Holub)写道,

没有耦合就不可能有一个程序。尽管如此,您可以通过盲目地遵循 OO(面向对象)规则来大大减少耦合(最重要的是对象的实现应该对使用它的对象完全隐藏)。例如,对象的实例变量(不是常量的成员字段)应该始终是私有的。时期。没有例外。曾经。我是认真的。(你偶尔可以有效地使用受保护的方法,但受保护的实例变量是可憎的。)

这听起来很合理,但他接着说,

出于同样的原因,您永远不应该使用 get/set 函数——它们只是将字段公开的过于复杂的方法(尽管返回完整对象而不是基本类型值的访问函数在返回对象的情况下是合理的)类是设计中的关键抽象)。

坦率地说,这对我来说听起来很疯狂。

我了解信息隐藏的原理,但是如果没有访问器和修改器,您根本无法使用 Java bean。我不知道如果模型中没有访问器,您将如何遵循 MVC 设计,因为模型不能负责呈现视图。

然而,我是一个年轻的程序员,我每天都在学习更多关于面向对象设计的知识。也许有更多经验的人可以在这个问题上权衡。

Allen Holub 的文章供参考


相关问题:

4

13 回答 13

40

我对 Holub 告诉您通常应该避免更改对象的状态,而是采用集成方法(行为的执行)来实现这一目的没有意见。正如 Corletk 所指出的那样,对最高级别的抽象进行长时间而艰苦的思考是有智慧的,而不仅仅是使用 getter/setter 进行粗心的编程,而只是让你在封装中进行最终运行。

但是,如果有人告诉您应该“从不”使用 setter 或“从不”访问原始类型,我会遇到很多麻烦。事实上,在所有情况下保持这种纯度水平所需的努力最终会导致您的代码比使用适当实现的属性更复杂。您只需要有足够的意识就可以知道何时以牺牲长期痛苦为代价来规避短期收益的规则。

Holub 不相信您知道其中的区别。我认为了解差异是使您成为专业人士的原因。

于 2009-06-15T15:08:56.197 回答
33

仔细阅读那篇文章。Holub 强调 getter 和 setter 是一种邪恶的“默认反模式”,这是我们在设计系统时陷入的坏习惯;因为我们可以。

思考过程应该顺理成章;这个对象有什么作用?它的职责是什么?它的行为是什么?它知道什么?对这些问题进行长时间而认真的思考会自然而然地引导您设计尽可能公开最高级别接口的类。

汽车就是一个很好的例子。它公开了一个定义明确、标准化的高级接口。我不关心setSpeed(60)...是MPH还是km/h?我只是加速,巡航,减速。我不必考虑其中的细节setSteeringWheelAngle(getSteeringWheelAngle()+Math.rad(-1.5)),我只是,细节在引擎盖下turn(-1.5)得到了照顾。

它归结为“您可以并且应该弄清楚每个类将用于什么,它做什么,它代表什么,并公开满足这些要求的可能的最高级别接口。getter 和 setter 通常是一种逃避,当程序员只是懒得做分析以确定每个类是什么和不是什么,所以我们走上了“它可以做任何事情”的道路。getter 和 setter 是邪恶的!

有时,一个类的实际要求是无法提前知道的。这很酷,只是暂时退出并使用 getter/setter 反模式,但是当您通过经验知道该类的用途时,您可能会想要回来并清理肮脏的低级接口。基于“一开始写傻瓜时你希望知道的东西”进行重构是本课程的标准。您不必了解所有事情才能开始,只是您知道的越多,在此过程中可能需要的返工就越少。

这就是他所提倡的心态。Getter 和 Setter 是一个容易掉入的陷阱。

是的,bean 基本上需要 getter 和 setter,但对我来说,bean 是一个特例。Beans 代表名词、事物、有形的可识别(如果不是物理的)对象。实际上并不是很多对象都有自动行为;大多数时候,事物是由包括人类在内的外部力量操纵的,以使它们具有生产力。

daisy.setColor(Color.PINK)很有意义。你还能做什么?也许是瓦肯人的心灵融合,让花朵想要变成粉红色?嗯?

吸气剂和二传手有他们的?邪恶?地方。只是,就像所有真正好的 OO 东西一样,我们倾向于过度使用它们,因为它们是安全和熟悉的,更不用说简单了,因此如果菜鸟没有看到或听到它们可能会更好,至少在他们之前” d 掌握了心灵融合的东西。

于 2009-06-15T14:20:53.520 回答
31

我认为 Allen Holub 试图在本文中重新表述内容如下。

Getter 和 setter 对于您特别想要封装的变量很有用,但您不必对所有变量都使用它们。事实上,将它们用于所有变量是令人讨厌的代码气味。

程序员遇到的麻烦,Allen Holub 指出的正确,是他们有时对所有变量都使用 getter/setter。并且失去了封装的目的。

于 2009-06-15T13:48:25.093 回答
10

(请注意,我是从 .NET“属性”角度来看的)

好吧,简单地说——我不同意他的观点;他对属性的返回类型是一件坏事大惊小怪,因为它可能会破坏您的调用代码 - 但完全相同的参数将适用于方法参数。如果你也不能使用方法?

好的,方法参数可以随着扩大转换而更改,但是....为什么...另外,请注意,在 C# 中,var关键字可以减轻很多这种感知的痛苦。

访问器不是实现细节;它们是公共 API / 合约。是的,如果你违反合同,你就有麻烦了。什么时候变成了意外?同样,访问器也很重要——也就是说,它们不仅仅是包装字段。它们执行计算、逻辑检查、通知等。它们允许基于接口的状态抽象。哦,还有多态性 - 等等。

访问器的详细性质(p3?4?) - 在 C# 中:public int Foo {get; private set;}- 工作完成。

最终,所有代码都是向编译器表达我们意图的一种方式。属性让我以类型安全、基于契约、可验证、可扩展、多态的方式做到这一点——谢谢。为什么我需要“修复”这个?

于 2009-06-15T13:49:20.617 回答
9

Getter 和 setter 仅用作使私有变量公开的掩码。

重复 Holub 已经说过的话是没有意义的,但关键是类应该代表行为而不仅仅是状态。

于 2009-06-15T13:49:15.810 回答
8

一些反对意见用斜体表示:

虽然 getIdentity 以“get”开头,但它不是访问器,因为它不只是返回一个字段。它返回一个具有合理行为的复杂对象

哦,但是等等……那么只要返回对象而不是原始类型就可以使用访问器?现在这是一个不同的故事,但它对我来说同样愚蠢。有时你需要一个对象,有时你需要一个原始类型。

另外,我注意到,自从他上一篇关于同一主题的专栏文章以来,艾伦已经彻底软化了他的立场,“从不使用访问器”的口头禅没有一次例外。也许几年后他意识到访问器毕竟是有目的的……

请记住,我实际上并没有将任何 UI 代码放入业务逻辑中。我用 AWT(Abstract Window Toolkit)或 Swing 编写了 UI 层,它们都是抽象层。

好一个。如果您在 SWT 上编写应用程序怎么办?在那种情况下,AWT 到底有多“抽象”?面对现实吧:这个建议只会引导您在业务逻辑中编写 UI 代码。多么伟大的原则。毕竟,距离我们将这种做法确定为您在项目中可以做出的最糟糕的设计决策之一,这仅仅过去了至少十年。

我的问题是,作为一个新手程序员,有时会偶然发现互联网上的文章,然后给他们更多的信任。也许这是其中一种情况。

于 2009-06-15T13:56:02.753 回答
5

当像这样的想法出现在我面前时,我喜欢看一下我使用的以及我喜欢使用的库和框架。

例如,尽管有些人不同意,但我喜欢 Java 标准 API。我也喜欢 Spring 框架。查看这些库中的类,您会注意到很少有 setter 和 getter 只是为了公开一些内部变量。有一些名为getX 的方法,但这并不意味着它是传统意义上的 getter。

所以,我认为他有一个观点,那就是:每次您在 Eclipse(或您选择的 IDE)中按选择“生成 getters/setters”时,您应该退后一步,想知道您在做什么。公开这种内部表示是否真的合适,还是我在某个步骤搞砸了我的设计?

于 2010-03-04T20:57:07.643 回答
2

我不相信他说永远不要使用get/set,而是对字段使用get/set 并不比仅仅公开该字段更好(例如,公共字符串名称与公共字符串名称{get; set; })。

如果使用 get/set,它会限制 OO 的信息隐藏,这可能会将您锁定在错误的界面中。

在上面的例子中,Name 是一个字符串;如果我们以后想更改设计以添加多个名称怎么办?该接口只暴露了一个字符串,所以我们不能在不破坏现有实现的情况下添加更多。

但是,如果您最初使用 Add(string name) 之类的方法而不是使用 get/set,则在内部您可以单独处理 name 或添加到列表中,或者在外部调用 Add 方法的次数与您想添加的次数一样多更多名称。

OO 的目标是使用抽象级别进行设计;不要暴露比你绝对必须更多的细节。

如果您刚刚使用 get/set 包装了一个原始类型,那么您可能已经打破了这个原则。

当然,这是如果您相信 OO 目标;我发现大多数不是,不是真的,他们只是使用对象作为对功能代码进行分组的一种方便的方式。

于 2009-06-15T14:13:54.957 回答
2

当类只不过是一堆没有真正连贯性的数据时,或者当它非常非常基本(例如点类)时,公共变量才有意义。一般来说,如果一个类中有任何你认为可能不应该公开的变量,这意味着该类具有一定的一致性,并且变量具有应该保持的某种关系,因此所有变量都应该是私有的。

当 getter 和 setter 反映某种连贯的想法时,它们才有意义。例如,在多边形类中,给定顶点的 x 和 y 坐标在类边界之外具有意义。拥有 getter 可能是有意义的,拥有 setter 可能是有意义的。在银行帐户类中,余额可能存储为私有变量,并且几乎可以肯定应该有一个 getter。如果它有一个设置器,它需要内置日志记录以保持可审计性。

与公共变量相比,getter 和 setter 有一些优势。它们提供了接口和实现的某种分离。仅仅因为一个点有一个.getX()函数并不意味着必须有一个 x,因为.getX().setX()可以在径向坐标下正常工作。另一个是可以通过做任何必要的事情来保持类在setter中的一致性来维护类不变量。另一个是可以具有触发集合的功能,例如记录银行账户余额。

然而,对于更抽象的类,成员变量失去了单独的意义,只在上下文中才有意义。例如,您不需要知道 C++ 流类的所有内部变量。您需要知道如何让元素进出,以及如何执行各种其他操作。如果您指望确切的内部结构,您会陷入细节的困境,这些细节可能会在编译器或版本之间任意变化。

所以,我想说几乎只使用私有变量,getter 和 setter,它们在对象行为中具有真正的意义,而不是其他。

仅仅因为 getter 和 setter 经常被过度使用并不意味着它们是无用的。

于 2009-06-15T15:33:44.687 回答
2

getter/setter 的问题在于它们试图伪造封装,但实际上通过暴露其内部结构来破坏封装。其次,他们试图做两件不同的事情——提供访问和控制他们的状态——但最终都做得不好。

它破坏了封装,因为当您调用 get/set 方法时,您首先需要知道要更改的字段的名称(或有一个好主意),其次您必须知道它的类型,例如。你不能打电话

setPositionX("some string");

如果你知道字段的名称和类型,并且 setter 是公共的,那么任何人都可以像调用公共字段一样调用该方法,这只是一种更复杂的方法,所以为什么不简化它并制作它首先是一个公共领域。

通过允许访问它的状态但同时试图控制它,get/set 方法只会混淆事物并最终成为无用的样板,或误导,实际上并没有按照它所说的那样做用户可能没有预料到的效果。如果需要错误检查,可以将其称为类似

public void tryPositionX(int x) throws InvalidParameterException{
    if (x >= 0)
        this.x = x;
    else
        throw new InvalidParameterException("Holy Negatives Batman!");
}

或者如果需要额外的代码,它可以根据整个方法的作用称为更准确的名称,例如。

tryPositionXAndLog(int x) throws InvalidParameterException{
    tryPositionX(x);
    numChanges++;
}

恕我直言,需要 getter/setter 来完成某些工作通常是糟糕设计的症状。利用“告诉,不要问”的原则,或者重新思考为什么一个对象首先需要发送它的状态数据。公开改变对象行为而不是状态的方法。这样做的好处包括更容易维护和增加可扩展性。

你也提到了 MVC 并说模型不能对它的视图负责,对于这种情况,Allen Holub 给出了一个通过“give-me-a-JComponent-that-represents-your-identity 类来制作抽象层的例子” ”他说这将“将身份表示方式与系统的其余部分隔离开来。” 我没有足够的经验来评论这是否可行,但从表面上看,这听起来不错。

于 2011-10-08T22:05:25.473 回答
1

如果公共 getter/setter 提供对实现细节的访问,那么它们是不好的。然而,提供对对象属性的访问并为此使用 getter/setter 是合理的。例如,如果 Car 具有颜色属性,则可以让客户使用 getter 来“观察”它。如果某些客户需要重新着色汽车的能力,该类可以提供一个 setter(虽然“recolor”是更清晰的名称)。重要的是不要让客户知道属性是如何存储在对象中的,它们是如何维护的,等等。

于 2010-03-04T20:22:56.807 回答
0

嗯……他没听说过封装的概念吗?Getter 和 Setter 方法用于控制对类成员的访问。通过使所有字段公开可见……任何人都可以向他们写入他们想要的任何值,从而使整个对象完全无效。

万一有人对封装的概念有点模糊,请在此处阅读:

封装(计算机科学)

...如果它们真的很邪恶,.NET 会将属性概念构建到语言中吗?(Getter 和 Setter 方法看起来更漂亮一些)

编辑

文章确实提到了封装:

getter 和 setter 对于您特别想要封装的变量可能很有用,但您不必对所有变量都使用它们。事实上,对所有变量使用它们是令人讨厌的代码味道。

从长远来看,使用这种方法将导致代码极难维护。如果您在一个跨越数年的项目中发现需要封装一个字段,您将不得不在软件中的任何地方更新该字段的每个引用以获得好处。预先使用适当的封装并在以后避免头痛,这听起来要聪明得多。

于 2009-06-15T13:47:54.580 回答
0

我认为 getter 和 setter 应该只用于需要在类之外访问或更改的变量。话虽如此,我不认为变量应该是公开的,除非它们是静态的。这是因为将非静态变量公开可能会导致它们被不希望地更改。假设您有一个不小心使用公共变量的开发人员。然后,他从另一个类访问一个变量,并且毫无意义地更改它。现在,由于这次事故,他的软件出现了错误。这就是为什么我相信正确使用 getter 和 setter,但你不需要它们用于每个私有或受保护的变量。

于 2009-06-19T06:05:24.597 回答