关于java:getter和setter设计不佳?

Are getters and setters poor design? Contradictory advice seen

本问题已经有最佳答案,请猛点这里访问。

我目前正在使用Java中的几种简单模式进行简单的游戏。我扩展了一个主游戏类,将主逻辑放在其他类中。尽管如此,主要游戏类仍然相当庞大。

在快速查看了我的代码之后,大多数代码是getter和setter(60%),而其他代码则是游戏逻辑所真正需要的。

一些谷歌搜索声称getter和setter是邪恶的,而另一些则声称它们对于良好的OO实践和伟大的程序是必要的。

那我该怎么办?应该是哪个?我应该为我的私有变量更改getter和setter,还是应该坚持使用它们?


还有一种观点认为,大多数情况下,使用setter仍然会通过允许您设置无意义的值来破坏封装。作为一个非常明显的例子,如果你在比赛中有一个计分器只会上升,而不是

1
2
3
4
5
6
// Game
private int score;
public void setScore(int score) { this.score = score; }
public int getScore() { return score; }
// Usage
game.setScore(game.getScore() + ENEMY_DESTROYED_SCORE);

应该是

1
2
3
4
5
6
// Game
private int score;
public int getScore() { return score; }
public void addScore(int delta) { score += delta; }
// Usage
game.addScore(ENEMY_DESTROYED_SCORE);

这也许是一个轻而易举的例子。我想说的是,讨论getter/setter和public字段通常会掩盖对象以亲密方式操纵彼此内部状态的更大问题,从而使对象之间的关系过于紧密。

这个想法是让方法直接做你想做的事情。例如如何设置敌人的"生存"状态。您可能会尝试使用setalive(boolean-alive)方法。相反,你应该有:

1
2
3
private boolean alive = true;
public boolean isAlive() { return alive; }
public void kill() { alive = false; }

原因是,如果您更改了实现,即事物不再具有"活动"布尔值,而是具有"命中点"值,那么您可以在不破坏先前编写的两个方法的契约的情况下更改该值:

1
2
3
4
private int hp; // Set in constructor.
public boolean isAlive() { return hp > 0; } // Same method signature.
public void kill() { hp = 0; } // Same method signature.
public void damage(int damage) { hp -= damage; }


  • 非常邪恶:公共领域。
  • 有点邪恶:在不需要的地方获取和设置。
  • 好:getter和setter只在它们真正需要的地方——让类型公开碰巧使用其状态的"更大"行为,而不仅仅是将类型视为由其他类型操纵的状态存储库。

不过,这确实取决于具体情况——有时您真的只需要一个愚蠢的数据对象。


关于这个问题,你已经有很多好的答案了,所以我只给你两分钱。能手和二传手非常非常邪恶。当你所做的大部分时间都被扔到多余的代码中,这些代码对隐藏内部状态没有任何作用时,它们实际上让你假装隐藏了对象的内部。对于一个简单的pojo,没有理由不能用obj.name="tom"替换getname()和setname()。

如果方法调用仅仅替换了赋值,那么通过首选方法调用所获得的只是代码膨胀。不幸的是,语言已经在JavaBeans规范中使用了吸气剂和定位器,因此Java程序员被迫使用它们,即使这样做也毫无意义。

幸运的是,Eclipse(可能还有其他的IDE)允许您自动生成它们。为了一个有趣的项目,我曾经在XSLT中为它们构建了一个代码生成器。但是,如果有一件事我可以在Java中去掉,那就是过度依赖于吸气剂和定位器。


getter和setter在面向对象编程中实施封装的概念。

通过将物体的状态隐藏在外面的世界中,物体就真正地控制着自己,并且不能以非预期的方式改变。唯一可以操作对象的方法是通过公开的公共方法,如getter和setter。

拥有getter和setter有一些优势:

1。允许在不修改使用已修改类的代码的情况下进行将来的更改。

使用getter和setter的一大优势是,一旦定义了公共方法,并且在某个时候需要更改底层实现(例如,找到需要修复的bug,使用不同的算法来提高性能等),让getter和setter是操作对象,它将允许现有代码不中断,并在更改后按预期工作。

例如,假设有一个setValue方法在对象中设置value私有变量:

1
2
3
4
public void setValue(int value)
{
    this.value = value;
}

但是,有一个新的要求,需要跟踪value被更改的次数。有了setter,更改就相当微不足道了:

1
2
3
4
5
public void setValue(int value)
{
    this.value = value;
    count++;
}

如果value字段是公开的,那么很难稍后返回并添加一个计数器来跟踪值更改的次数。因此,拥有getter和setter是"证明未来"类的一种方法,用于以后可能发生的更改。

2。实施操纵物体的方法。

getter和setter的另一种使用方法是强制执行对象的操作方式,因此,对象处于其自身状态的控制中。暴露对象的公共变量后,它很容易被破坏。

例如,一个ImmutableArray对象包含一个名为myArrayint数组。如果数组是公共字段,它就不会是不可变的:

1
2
3
ImmutableArray a = new ImmutableArray();
int[] b = a.myArray;
b[0] = 10;      // Oops, the ImmutableArray a's contents have been changed.

要实现真正不可变的数组,应编写数组的getter(getArray方法),以便它返回其数组的副本:

1
2
3
4
public int[] getArray()
{
    return myArray.clone();
}

即使发生以下情况:

1
2
3
ImmutableArray a = new ImmutableArray();
int[] b = a.getArray();
b[0] = 10;      // No problem, only the copy of the array is affected.

ImmutableArray确实是不变的。公开一个对象的变量将允许它以非预期的方式被操作,但只公开某些方式(getter和setter),对象可以以预期的方式被操作。

我认为对于将由其他人使用的API的一部分的类来说,拥有getter和setter更为重要,因为它允许保持API的完整性和不变,同时允许对底层实现进行更改。

有了getter和setter的所有优点,如果getter只是返回私有变量的值,而setter只是接受一个值并将其分配给私有变量,那么getter和setter似乎是多余的,实际上是浪费。如果类只供不被其他人使用的应用程序内部使用,那么广泛使用getter和setter可能不像编写公共API那样重要。


他们绝对是邪恶的。

@不幸的是,Coobird绝对不会"强制执行封装的概念",他们所做的只是让你认为你是在封装数据,而实际上你是在通过一个具有方法宏伟幻想的属性来暴露数据。getter/setter在公共字段中做的任何事情都会做得更好。

首先,如果您想要公开数据,那么就将其公开,去掉getter&setter方法,以减少客户需要处理的方法的数量,并使客户在认知上更简单地通过eg更改其值。

1
object.field = value;

而不是更强烈的认知

1
object.setField(value);

客户机现在必须检查getter/setter方法,看看它是否有任何副作用。

第二,如果你真的需要在方法中做一些其他的事情,为什么当它有比简单的获取或设置更多的职责时,就称它为get/set方法呢?要么遵循SRP,要么调用这个方法,它实际上告诉你整个方法的工作原理,就像他提到的扎克南的例子,例如。

1
2
3
4
public void kill(){
    isAlive = false;
    removeFromWorld(this);
}

而不是

1
2
3
4
5
6
7
public void setAlive(boolean isAlive){
    this.isAlive = isAlive;
    if (isAlive)
        addToWorld(this);
    else
        removeFromWorld(this);
}

setalive(布尔)方法在哪里告诉客户机,作为副作用,它将从世界上删除对象?为什么客户应该对isalive领域有任何了解?另外,当对象重新添加到世界中时会发生什么,是否应该重新初始化?为什么客户会关心这些?

我的道德观是命名方法来准确地说出他们所做的,遵循SRP并摆脱getter/setter。如果没有getter/setter存在问题,请告诉对象在自己的类中执行自己的脏工作,而不是在其他类中尝试使用它们。

我的咆哮到此结束,对此感到抱歉;)


这是一个很滑的斜坡。

简单的传递对象(或参数对象)可能唯一的目的是保存一些字段并根据需要提供它们的值。然而,即使在这种退化的情况下,也有人认为对象应该是不可变的——在构造函数中配置,并且只公开get…方法。

还有一个例子,一个类暴露了一些"控制旋钮";你的车载收音机的用户界面可能被理解为暴露了一些东西,比如getVolumesetVolumegetChannelsetChannel,但它真正的功能是接收信号和发出声音。但是这些旋钮并没有暴露很多实现细节;从这些接口特性中你不知道收音机是晶体管,主要是软件,还是真空管。

你越是开始把一个对象看作是问题域任务中的一个积极参与者,你就越是想让它做些什么,而不是让它告诉你关于它的内部状态,或者要求它提供数据,这样其他代码就可以用这些值做些什么。

所以……"邪恶?不是真的。但每次你倾向于输入一个值并同时公开get时…和set…方法在这个值上,问自己为什么,以及这个对象的可响应性到底是什么。如果你能给自己的唯一答案是"为我保留这个价值",那么也许除了OO之外,还有别的事情在这里发生。


如果你的游戏类暴露了那么多的变量,那么它很可能遵循上帝对象反模式。GETSter和SETTER没有什么错(尽管Java中的冗长性可能有点讨厌);在一个设计良好的应用程序中,每个类都有一个清晰分离的功能,在一个类中您不需要几十个。

编辑:如果getter和setter的要点是"配置"游戏类(我这样理解您的评论),那么您可能不需要getter(类访问自己的私有变量而不使用get方法是非常好的),并且您可能会将许多setter折叠成"group setters",将它们设置为概念上属于一起的变量。


getter和setter的存在往往意味着(如果你使用的是那种小学语言,那就有一种"气味")存在设计问题。微不足道的getter和setter几乎无法与公共领域区分开来。通常,在数据上操作的代码将在另一个类中——糟糕的封装,以及程序员对OO不放心的期望。

在某些情况下,getter和setter是很好的。但是,通常,同时具有getter和setter的类型指示设计问题。getter为不变而工作;setter为"tell don't ask"工作。不变性和"告诉不要问"都是很好的设计选择,只要它们不应用于重叠样式。


我的观点是getter和setter是好程序的一个要求。坚持使用它们,但不要编写不必要的getter/setter——并不总是需要直接处理所有变量。


我并不认为他们是邪恶的。但我希望生活在一个我从来不用它们的世界里,除非我真的需要。

我在上面读到的一个例子是future-proofing您的代码。例如:

1
2
3
4
public void setValue(int value)
{
    this.value = value;
}

然后,需求更改,您需要跟踪设置值的次数。

所以:

1
2
3
4
5
public void setValue(int value)
{
    this.value = value;
    count++;
}

这很漂亮。我明白了。然而,在Ruby中,下面的内容是否有同样的用途?

1
someobject.my_value = 100

稍后,您需要跟踪my_value设置的次数。那么,你能不能不只是重写setter,然后就重写setter?

1
2
3
4
def my_value=(value)
    @my_value = value
    @count++
end

我都是为了美丽的代码,但我不得不承认,通过我们所拥有的Java类,看到成千上万的代码行,只不过是基本的吸气剂/设定器是丑陋的和恼人的。

当我在C全职开发时,我们一直使用公共属性,并且只在需要时定制getter/setter。像个魔咒一样工作,什么都没破。


一如既往,唯一的答案是:视情况而定。如果你是唯一一个接触代码的人,你可以做任何你喜欢做的事情,包括走捷径。

使用setter的好处之一是只需要在代码中的一个位置执行检查。

您可能需要更密切地关注这些方法实际获得和设置的内容。如果您使用它们来提供对常量值的访问,那么使用常量可能会更好。


这取决于所讨论的编程语言。你的问题是在Java的框架下进行的,在那里,吸气剂和定位器一般被认为是一件好事。

相反,在Python世界中,它们通常被认为是不好的样式:它们向代码添加行,而实际上不添加功能。当Python程序员需要时,他们可以使用元编程来获取和/或设置对象属性。

在Java中(至少是10年前我学习的Java版本),这是不可能的。因此,在Java中,通常最好使用吸气剂和设置器,因此,如果需要的话,可以重写对变量的访问。

(这并不意味着Python一定要比Java好,只是不同而已。)


仅供参考:除了这条线索中所有优秀的答案之外,请记住,在所有你能想出支持或反对getter/setter的原因中,性能不是一个(正如有些人可能相信的那样)。JVM足够聪明,可以嵌入一些简单的getter/setter(即使是非final个getter/setter,只要它们实际上没有被覆盖)。


如果需要外部访问各个字段值,请使用getter和/或setter。如果没有,不要。不要使用公共领域。就这么简单!(好吧,从来没有那么简单,但这是一个很好的经验法则)。

一般来说,您还应该发现,与getter相比,您需要提供setter的频率要少得多——特别是如果您试图使对象不可变——这是一件好事(但并不总是最佳选择),但即使不是这样。


您可能希望用值类替换一些类。这将允许您删除getter,并避免在内容从您下面更改时出现问题。


我已经在Java中编程了几个月以前,我知道只有在应用程序需要的时候,我们才应该使用GETTER和SETSET。

玩得开心: