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是操作对象,它将允许现有代码不中断,并在更改后按预期工作。
例如,假设有一个
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++; } |
如果
2。实施操纵物体的方法。
getter和setter的另一种使用方法是强制执行对象的操作方式,因此,对象处于其自身状态的控制中。暴露对象的公共变量后,它很容易被破坏。
例如,一个
1 2 3 | ImmutableArray a = new ImmutableArray(); int[] b = a.myArray; b[0] = 10; // Oops, the ImmutableArray a's contents have been changed. |
要实现真正不可变的数组,应编写数组的getter(
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. |
我认为对于将由其他人使用的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存在问题,请告诉对象在自己的类中执行自己的脏工作,而不是在其他类中尝试使用它们。
我的咆哮到此结束,对此感到抱歉;)
这是一个很滑的斜坡。
简单的传递对象(或参数对象)可能唯一的目的是保存一些字段并根据需要提供它们的值。然而,即使在这种退化的情况下,也有人认为对象应该是不可变的——在构造函数中配置,并且只公开
还有一个例子,一个类暴露了一些"控制旋钮";你的车载收音机的用户界面可能被理解为暴露了一些东西,比如
你越是开始把一个对象看作是问题域任务中的一个积极参与者,你就越是想让它做些什么,而不是让它告诉你关于它的内部状态,或者要求它提供数据,这样其他代码就可以用这些值做些什么。
所以……"邪恶?不是真的。但每次你倾向于输入一个值并同时公开
如果你的游戏类暴露了那么多的变量,那么它很可能遵循上帝对象反模式。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——并不总是需要直接处理所有变量。
我并不认为他们是邪恶的。但我希望生活在一个我从来不用它们的世界里,除非我真的需要。
我在上面读到的一个例子是
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 |
稍后,您需要跟踪
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(即使是非
如果需要外部访问各个字段值,请使用getter和/或setter。如果没有,不要。不要使用公共领域。就这么简单!(好吧,从来没有那么简单,但这是一个很好的经验法则)。
一般来说,您还应该发现,与getter相比,您需要提供setter的频率要少得多——特别是如果您试图使对象不可变——这是一件好事(但并不总是最佳选择),但即使不是这样。
您可能希望用值类替换一些类。这将允许您删除getter,并避免在内容从您下面更改时出现问题。
我已经在Java中编程了几个月以前,我知道只有在应用程序需要的时候,我们才应该使用GETTER和SETSET。
玩得开心: