我目前正在开发一个简单的Java游戏,有几种不同的模式.我扩展了一个主要的Game类,将主要逻辑放在其他类中.尽管如此,主要的游戏类仍然非常沉重.
在快速浏览一下我的代码后,其中大部分是Getters and Setters(60%),而其余部分则是游戏逻辑真正需要的.
一些谷歌搜索声称Getters和Setters是邪恶的,而其他人声称他们是良好的OO练习和伟大的程序所必需的.
所以我该怎么做?应该是哪个?我应该为我的私人变量更改我的Getters和Setter,还是应该坚持使用它们?
还有一种观点认为,大多数情况下,使用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; }
非常邪恶:公共领域.
有点邪恶:吸气剂和制定者他们不需要.
好:只有在真正需要的地方才能获得getter和setter - 使类型暴露出"更大"的行为,恰好使用其状态,而不仅仅是将类型视为由其他类型操纵的状态存储库.
这真的取决于形势虽然-有时候你真的不只是想要一个愚蠢的数据对象.
你已经有了很多很好的答案,所以我只给我两分钱.吸气剂和二传手非常非常邪恶.它们基本上让你假装隐藏你的对象的内部,当你所做的大部分时间被抛在冗余的代码中,没有什么可以隐藏内部状态.对于一个简单的POJO,没有理由不能用obj.name ="Tom"替换getName()和setName().
如果方法调用只是替换赋值,那么通过优先使用方法调用获得的只是代码膨胀.不幸的是,该语言已经在JavaBeans规范中规定了getter和setter的使用,因此Java程序员被迫使用它们,即使这样做也毫无意义.
幸运的是,Eclipse(以及可能还有其他IDE)可以让您自动生成它们.对于一个有趣的项目,我曾经在XSLT中为它们构建了一个代码生成器.但是,如果有一件事我会在Java中摆脱,那就是过度依赖于getter和setter.
Getters和setter 在面向对象的编程中强制执行封装的概念.
通过使对象的状态对外部世界隐藏,对象真正掌控自己,并且不能以非预期的方式改变.可以操纵对象的唯一方法是通过公开的公共方法,例如getter和setter.
获得getter和setter有一些好处:
1.允许将来更改而不修改使用修改后的类的代码.
使用getter和setter的一大优势是,一旦定义了公共方法,并且需要更改底层实现(例如,找到需要修复的错误,使用不同的算法来提高性能)等等,通过让getter和setter成为操作对象的唯一方法,它将允许现有代码不会中断,并且即使在更改之后也能按预期工作.
例如,假设有一个setValue
方法可以value
在对象中设置私有变量:
public void setValue(int value) { this.value = value; }
但是,有一个新的要求需要跟踪value
更改的次数.有了setter,变化相当简单:
public void setValue(int value) { this.value = value; count++; }
如果该value
字段是公开的,则没有简单的方法可以稍后返回并添加一个计数器来跟踪值的更改次数.因此,拥有getter和setter是一种"面向未来"的方法,可以在以后进行更改.
2.强制执行操作对象的方法.
getter和setter派上用场的另一种方式是强制操作对象的方式,因此,对象控制着自己的状态.使用公开的对象公开变量,它很容易被破坏.
例如,一个ImmutableArray
对象包含一个int
名为的数组myArray
.如果数组是公共字段,它就不会是不可变的:
ImmutableArray a = new ImmutableArray(); int[] b = a.myArray; b[0] = 10; // Oops, the ImmutableArray a's contents have been changed.
要实现真正不可变的数组,getArray
应该编写数组(方法)的getter,以便返回其数组的副本:
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.
这ImmutableArray
确实是不可改变的.公开对象的变量将允许以非预期的方式操纵它,但仅暴露某些方式(获取者和设置者),可以以预期的方式操纵对象.
我认为让getter和setter对于那些将被其他人使用的API的类更重要,因为它允许保持API完整和不变,同时允许更改底层实现.
有了getter和setter的所有优点,如果getter只是返回私有变量的值而setter只是接受一个值并将它赋给私有变量,那么getter和setter似乎只是无关紧要而且真的是浪费.如果该类仅供其他人不会使用的应用程序内部使用,则广泛使用getter和setter可能不如编写公共API时那么重要.
他们绝对是邪恶的.
@coobird不幸的是他们绝对没有"强制执行封装的概念",他们所做的就是让你认为你正在封装数据,而实际上你是通过一个具有妄想宏观方法的属性来暴露数据.任何getter/setter做公共领域都会做得更好.
首先,如果你想要公共数据,公开数据,摆脱getter和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的情况下出现问题,请告诉对象在自己的类中执行自己的脏工作,而不是尝试在其他类中使用它们.
在这里我的咆哮,对此感到抱歉;)
这是一个滑坡.
简单的Transfer对象(或Parameter对象)的唯一目的可能是保留一些字段并按需提供它们的值.然而,即使在那种退化的情况下,也可以认为对象应该是不可变的 - 在构造函数中配置并仅暴露get
...方法.
还有一个类暴露了一些"控制旋钮"的情况; 你的汽车收音机的用户界面大概可以理解为暴露类似getVolume
,setVolume
,getChannel
,和setChannel
,但其真正的功能是接收信号并发出声音.但是那些旋钮并没有暴露出太多的实现细节; 你不知道这些接口功能是无线电是晶体管,主要是软件,还是真空管.
您开始将对象视为问题域任务的主动参与者越多,您就会越多地考虑要求它做某事而不是让它告诉您其内部状态,或者要求它它的数据使其他代码可以对这些值做些什么.
那么......"邪恶"?并不是的.但是每次你倾向于投入一个价值并在这个价值上暴露get
......和set
......方法时,问问自己为什么,以及那个对象的真实性是什么.如果你能给自己的唯一答案就是"为我保留这个价值",那么除了OO之外还有其他的东西可以在这里进行.
如果它暴露了许多变量,你的Game类可能会跟随神对象反模式.getter和setter没有任何问题(虽然他们在Java中的冗长可能有点烦人); 在一个设计良好的应用程序中,每个类具有明确分离的功能,在单个类中不需要数十个.
编辑:如果getter和setter的要点是"配置"游戏classe(我理解你的评论方式),那么你可能不需要getter(对于一个类来说访问它自己的私有变量是完全正确的不使用get方法),你可以将许多setter折叠成"group setter",它们设置了几个概念上属于一起的变量.
吸气剂和二传手的存在往往表明(如果你是那种小学语言的"气味")存在设计问题.琐碎的吸气剂和制定者几乎与公共领域不同.通常,对数据进行操作的代码将处于不同的类别 - 封装不良,以及程序员对OO不太满意的期望.
在某些情况下,吸气剂和制定者都可以.但通常,具有getter和setter的类型表示设计问题.吸气剂为不变性而工作; 制定者为"告诉别问"工作.不变性和"告诉不要问"都是很好的设计选择,只要它们不是以重叠的方式应用.
我的观点是,吸气剂和制定者是良好计划的要求.坚持使用它们,但不要编写不必要的getter/setter - 并不总是需要直接处理所有变量.
我真的不认为他们是邪恶的.但我希望生活在一个我从未使用它们的世界,除非我真的需要.
我上面读到的一个例子是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
设置的次数.那么,你能不能只覆盖二传手THEN只有THEN?
def my_value=(value) @my_value = value @count++ end
我只是为了美丽的代码,但我必须承认,通过我们拥有的Java类的山脉,看到成千上万的代码行,但基本的getter/setter是丑陋和烦人的.
当我在C#全职开发时,我们一直使用公共属性,并且仅在需要时才使用自定义getter/setter.工作就像一个魅力,它没有破坏任何东西.