我目前正在开发一个简单的Java游戏,有几种不同的模式。我扩展了一个主要的Game类,将主要逻辑放在其他类中。尽管如此,主要的游戏类仍然相当沉重。
在快速浏览一下我的代码后,其中大部分是Getters and Setters(60%),而其余部分则是游戏逻辑真正需要的。
谷歌的一些搜索声称Getters和Setters是邪恶的,而其他人声称他们是良好的OO练习和优秀程序所必需的。
那我该怎么办?应该是哪个?我应该为我的私人变量更改我的Getters和Setter,还是应该坚持使用它们?
答案 0 :(得分:342)
还有一种观点认为,大多数情况下,使用setter仍然会通过允许您设置无意义的值来打破封装。作为一个非常明显的例子,如果你在游戏中有一个得分计数器,那只会上升,而不是
// 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);
应该是
// Game
private int score;
public int getScore() { return score; }
public void addScore(int delta) { score += delta; }
// Usage
game.addScore(ENEMY_DESTROYED_SCORE);
这可能是一个简单的例子。我想说的是,讨论getter / setter与公共领域的关系经常掩盖了更大的问题,即对象以一种亲密的方式操纵彼此的内部状态,因此过于紧密耦合。
我们的想法是制定直接做你想做的事情的方法。一个例子是如何设置敌人的“活着”状态。您可能想要使用setAlive(boolean alive)方法。相反,你应该:
private boolean alive = true;
public boolean isAlive() { return alive; }
public void kill() { alive = false; }
这样做的原因是,如果您更改实现不再具有“活动”布尔值而非“生命值”值,则可以在不破坏前面编写的两种方法的合同的情况下更改它:
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; }
答案 1 :(得分:180)
这实际上取决于具体情况 - 有时你真的做只想要一个愚蠢的数据对象。
答案 2 :(得分:44)
你已经有了很多很好的答案,所以我只给我两分钱。吸气剂和二传手非常非常邪恶。它们基本上让你假装隐藏你的对象的内部,当你所做的大部分时间被抛在冗余的代码中,没有什么可以隐藏内部状态。对于一个简单的POJO,没有理由不能用obj.name =“Tom”替换getName()和setName()。
如果方法调用只是替换赋值,那么通过优先选择方法调用获得的只是代码膨胀。不幸的是,该语言已经在JavaBeans规范中规定了getter和setter的使用,因此Java程序员被迫使用它们,即使这样做也没有任何意义。
幸运的是,Eclipse(以及可能还有其他IDE)可以让您自动生成它们。对于一个有趣的项目,我曾经在XSLT中为它们构建了一个代码生成器。但是,如果有一件事我会在Java中摆脱它,那就是过度依赖于getter和setter。
答案 3 :(得分:27)
Getters和setter在面向对象编程中强制执行encapsulation的概念。
通过使对象的状态对外界隐藏,对象真正掌控自己,并且不能以非预期的方式进行更改。可以操纵对象的唯一方法是通过公开的公共方法,例如getter和setter。
拥有吸气剂和制定者有一些好处:
<强> 1。允许将来更改而不修改使用修改后的类的代码。
使用getter和setter的一大优势是,一旦定义了公共方法,并且需要更改底层实现(例如,找到需要修复的bug,使用不同的算法)为了提高性能等),通过让getter和setter成为操作对象的唯一方法,它将允许现有代码不会中断,并且即使在更改之后也能按预期工作。
例如,假设有一个setValue
方法在对象中设置value
私有变量:
public void setValue(int value)
{
this.value = value;
}
但是,有一个新要求需要跟踪value
被更改的次数。随着设置器的到位,这种变化相当微不足道:
public void setValue(int value)
{
this.value = value;
count++;
}
如果value
字段是公开的,则没有简单的方法可以稍后返回并添加一个计数器来跟踪值的更改次数。因此,拥有getter和setter是一种“面向未来”的方法,可以在以后对类进行更改。
<强> 2。强制执行操作对象的方法。
getter和setter派上用场的另一种方式是强制执行对象的操作方式,因此,对象可以控制自己的状态。使用对象的公共变量时,它很容易被破坏。
例如,ImmutableArray
对象包含一个名为int
的{{1}}数组。如果数组是公共字段,它就不会是不可变的:
myArray
要实现一个真正不可变的数组,应该编写数组的getter(ImmutableArray a = new ImmutableArray();
int[] b = a.myArray;
b[0] = 10; // Oops, the ImmutableArray a's contents have been changed.
方法),以便返回其数组的副本:
getArray
即使发生以下情况:
public int[] getArray()
{
return myArray.clone();
}
ImmutableArray a = new ImmutableArray();
int[] b = a.getArray();
b[0] = 10; // No problem, only the copy of the array is affected.
确实是不可变的。公开对象的变量将允许以非预期的方式操纵它,但只暴露某些方式(getter和setter),可以按照预期的方式操作对象。
我认为拥有getter和setter对于那些将被其他人使用的API的类更重要,因为它允许保持API完整和不变,同时允许更改底层实现。
具有getter和setter的所有优点,如果getter只是返回私有变量的值而setter只是接受一个值并将其赋值给私有变量,那么getter和setter似乎只是外来的真的很浪费如果该类仅供其他人不会使用的应用程序内部使用,那么广泛使用getter和setter可能不如编写公共API时那么重要。
答案 4 :(得分:26)
他们绝对是邪恶的。
不幸的是,@ coobird他们绝对没有“强制执行封装的概念”,他们所做的就是让你认为你正在封装数据,而事实上你是通过一个具有妄想方法宏观的属性来暴露数据。任何getter / setter做公共领域都会做得更好。首先,如果您想要公开数据,请将其公开,摆脱吸气剂和放大器。 setter方法减少客户端必须通过的方法的数量,并使其在认知上更简单,以便客户端通过例如改变它的值。
object.field = value;
而不是认知上更强烈的
object.setField(value);
客户端现在必须检查getter / setter方法以查看它是否有任何副作用。
其次,如果你真的需要在方法中做一些其他的事情,为什么当它比简单的获取或设置更多的责任时称之为get / set方法? 要么遵循SRP,要么调用方法,实际上告诉你整个方法对Zarkonnen的例子提到了什么,例如。
public void kill(){
isAlive = false;
removeFromWorld(this);
}
而不是
public void setAlive(boolean isAlive){
this.isAlive = isAlive;
if (isAlive)
addToWorld(this);
else
removeFromWorld(this);
}
setAlive(boolean)方法在哪里告诉客户端作为副作用它会从世界中删除对象?为什么客户对isAlive字段有任何了解?加上当对象重新添加到世界时会发生什么,如果它被重新初始化?为什么客户会关心这些呢?
恕我直言,道德是命名方法来准确说出他们做了什么,遵循SRP并摆脱吸气剂/制定者。 如果在没有getter / setter的情况下出现问题,请告诉对象在自己的类中执行自己的脏工作,而不是尝试在其他类中使用它们。
在这里我的咆哮,对此感到抱歉;)
答案 5 :(得分:19)
这是一个滑坡。
简单的Transfer对象(或Parameter对象)的唯一目的可能是保留一些字段并按需提供其值。但是,即使在这种退化的情况下,也可以说对象应该是不可变的 - 在构造函数中配置并仅暴露get
...方法。
还有一个类暴露了一些“控制旋钮”的情况;你的汽车收音机的用户界面可能被理解为暴露了诸如getVolume
,setVolume
,getChannel
和setChannel
之类的东西,但它的真正功能是接收信号并发出声音。但是那些旋钮并没有暴露出太多的实现细节;你不知道这些接口功能是无线电是晶体管,主要是软件,还是真空管。
您开始将对象视为问题域任务的主动参与者越多,您就会越多地考虑要求做某事而不是让它告诉它关于它的内部状态,或者询问它的数据,所以其他代码可以对这些值做些什么。
所以......“邪恶”?并不是的。但是每次你倾向于输入一个值并在该值上公开get
...和set
...方法时,问问自己为什么,以及该对象的可靠性是什么。如果您能给自己的唯一答案是“为我保留这个价值”,那么可能除OO之外的东西正在这里。
答案 6 :(得分:15)
如果游戏类暴露了许多变量,那么它可能会遵循god object反模式。 getter和setter没有任何问题(虽然他们在Java中的冗长可能有点烦人);在一个设计良好的应用程序中,每个类具有明显分离的功能,在单个类中不需要数十个。
编辑:如果getter和setter的要点是“配置”游戏classe(我理解你的评论那样),那么你可能不需要getter(它是完美的)对于一个类来说,可以在不使用get方法的情况下访问它自己的私有变量,并且你可以将许多setter折叠成“group setters”,它们设置了几个概念上属于的变量。
答案 7 :(得分:11)
吸气剂和二传手的存在往往表明(如果你是那种小学语言的“气味”)存在设计问题。琐碎的吸气剂和制定者几乎与公共领域不同。通常情况下,对数据进行操作的代码将处于不同的类中 - 封装不良,以及程序员对OO不太满意的期望。
在某些情况下,getter和setter都很好。但通常,具有getter和setter的类型表示设计问题。吸气剂为不变性而工作;制定者为“告诉别问”工作。不变性和“告诉不要问”都是很好的设计选择,只要它们不是以重叠的方式应用。
答案 8 :(得分:10)
我的观点是,吸气剂和制定者是良好计划的要求。坚持使用它们,但不要编写不必要的getter / setter - 并不总是需要直接处理所有变量。
答案 9 :(得分:9)
我真的不认为他们是邪恶的。但我希望生活在一个我从未使用它们的世界,除非我真的需要它。
我上面读到的一个例子是future-proofing
你的代码。例如:
public void setValue(int value)
{
this.value = value;
}
然后,需求发生变化,您需要跟踪设置值的次数。
所以:
public void setValue(int value)
{
this.value = value;
count++;
}
这很美。我知道了。但是,在Ruby中,以下内容是否会起到同样的作用?
someobject.my_value = 100
稍后,您需要跟踪my_value
的设置次数。那么,您是否可以覆盖设置者那么并且仅那么?
def my_value=(value)
@my_value = value
@count++
end
我只是为了美丽的代码,但我不得不承认,通过我们拥有的Java类的山脉,看到成千上万的代码行,但基本的getter / setter是丑陋和恼人的。
当我在C#全职开发时,我们一直使用公共属性,并且仅在需要时才使用自定义getter / setter。工作就像一个魅力,它没有破坏任何东西。
答案 10 :(得分:4)
一如既往,唯一的答案是:它取决于。如果你是唯一触及代码的人,你可以做任何你喜欢的事情,包括走捷径。
使用setter的一个好处是需要在代码中的一个位置执行检查。
您可能希望更密切关注这些方法实际获取和设置的内容。如果您使用它们来提供对常量值的访问,那么使用常量可能会更好。
答案 11 :(得分:3)
这取决于所讨论的编程语言。你的问题是在Java的背景下构建的,似乎getter和setter通常被认为是一件好事。
相比之下,在Python世界中,它们通常被认为是错误的样式:它们在代码中添加行而不实际添加功能。当Python程序员需要时,他们可以使用元编程来捕获对象属性的获取和/或设置。
在Java中(至少十年前我学到的Java版本),这是不可能的。因此,在Java中,通常最好使用getter和setter,这样如果需要,可以覆盖对变量的访问。
(这不会使Python必然比Java更好,只是不同。)
答案 12 :(得分:2)
仅供参考:除了这个帖子中的所有优秀答案之外,请记住,你可以提出或反对getter / setter的所有原因,性能不是一个(有些人可能会相信)。 JVM足够智能,可以内联琐碎的getter / setter(甚至非final
个,只要它们实际上没有被覆盖)。
答案 13 :(得分:1)
您可能希望按值类替换某些类。这将允许您删除getter并避免在您下面更改内容时出现问题。
答案 14 :(得分:1)
如果您需要外部访问各个字段值,请使用getter和/或setter。如果没有,不要。切勿使用公共领域。就这么简单! (好吧,它永远不会 简单,但这是一个很好的经验法则。)
一般来说,你也应该发现你需要提供一个比一个getter更少的setter - 特别是如果你试图使你的对象不可变 - 这是一件好事(但并不总是最好的选择) - 甚至如果没有。
答案 15 :(得分:0)
几个月前我一直在使用java编程,而且我已经知道我们应该使用getter&amp;只有当应用程序需要时才设置setter
玩得开心:)