getter 和 setter 的设计是否糟糕?看到矛盾的建议[重复]
题
这个问题在这里已经有答案了:
我目前正在用 Java 开发一个具有几种不同模式的简单游戏。我扩展了一个主 Game 类,将主要逻辑放入其他类中。尽管如此,主要的游戏类别仍然相当庞大。
快速浏览一下我的代码后,与游戏逻辑真正需要的其余部分相比,其中大部分是 Getters 和 Setters (60%)。
一些 Google 搜索声称 Getters 和 Setters 是邪恶的,而其他搜索则声称它们对于良好的 OO 实践和优秀的程序是必要的。
所以我该怎么做?应该是哪个?我应该为我的私有变量更改 Getter 和 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。
- 好的:Getters 和 Setters 仅在真正需要的地方 - 使类型暴露“更大”的行为 使用 它的状态,而不是仅仅将类型视为由其他类型操作的状态存储库。
不过,这确实取决于具体情况 - 有时你真的 做 只是想要一个愚蠢的数据对象。
你已经对此有很多好的答案,所以我只给出我的两分钱。getter 和 setter 非常非常邪恶。它们本质上是让你假装隐藏对象的内部结构,而大多数时候你所做的一切都被扔进了冗余代码,而这些代码对隐藏内部状态没有任何作用。对于一个简单的 POJO,没有理由不能将 getName() 和 setName() 替换为 obj.name = "Tom"。
如果方法调用仅仅取代了赋值,那么通过选择方法调用所获得的只是代码膨胀。不幸的是,该语言在 JavaBeans 规范中规定了 getter 和 setter 的使用,因此 Java 程序员被迫使用它们,即使这样做毫无意义。
幸运的是,Eclipse(可能还有其他 IDE)可以让您自动生成它们。为了一个有趣的项目,我曾经用 XSLT 为它们构建了一个代码生成器。但如果说我想在 Java 中摆脱一件事,那就是对 getter 和 setter 的过度依赖。
Getters 和 Setters 强化了以下概念: 封装 在面向对象编程中。
通过将对象的状态对外界隐藏起来,对象可以真正掌控自己,并且不能以非预期的方式进行更改。操作对象的唯一方法是通过公开的公共方法,例如 getter 和 setter。
使用 getter 和 setter 有一些优点:
1.允许将来更改而无需修改使用修改后的类的代码。
使用 getter 和 setter 的一大优点是,一旦定义了公共方法,有时就需要更改底层实现(例如找到需要修复的错误,使用不同的算法来提高性能等),通过让 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
数组称为 myArray
. 。如果数组是公共字段,它就不会是不可变的:
ImmutableArray a = new ImmutableArray();
int[] b = a.myArray;
b[0] = 10; // Oops, the ImmutableArray a's contents have been changed.
要实现真正的不可变数组,需要数组的 getter (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.
这 ImmutableArray
确实是一成不变的。暴露对象的变量将允许它以非预期的方式进行操作,但只有暴露某些方式(getter 和 setter),对象才能以预期的方式进行操作。
我认为对于属于将被其他人使用的 API 一部分的类来说,拥有 getter 和 setter 会更重要,因为它允许保持 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。如果没有 getter/setter 时出现问题,请告诉对象在自己的类中做自己的肮脏工作,而不是尝试在其他类中与它们一起做事情。
我的咆哮到此结束,对此感到抱歉;)
这是一个滑坡。
简单的 Transfer 对象(或 Parameter 对象)的唯一目的可能是保存某些字段并根据需要提供它们的值。然而,即使在这种退化的情况下,人们也可能会认为该对象应该是不可变的——在构造函数中配置并仅公开 get
...方法。
还有一个类公开了一些“控制旋钮”的情况;你的汽车收音机的用户界面可能可以理解为暴露类似的东西 getVolume
, setVolume
, getChannel
, , 和 setChannel
, ,但它的真正功能是接收信号和发出声音。但这些旋钮并没有暴露太多的实现细节;从这些接口特征中你不知道收音机是晶体管(主要是软件)还是真空管。
你越开始将一个对象视为问题域任务的积极参与者,你就越会考虑要求它做 做一点事 而不是要求它告诉您其内部状态,或者要求它提供数据 其他代码 可以用这些价值观做一些事情。
所以...“邪恶的”?并不真地。但每次你倾向于输入一个值并暴露两者时 get
...和 set
...方法,问问自己为什么,以及该对象的真正责任是什么。如果你能给自己的唯一答案是“为我保留这个价值”,那么 或许 这里发生了一些除了 OO 之外的事情。
您的 Game 类可能遵循 神物 反模式,如果它暴露了那么多变量。getter 和 setter 没有任何问题(尽管它们在 Java 中的冗长可能有点烦人);在一个设计良好的应用程序中,每个类都有明确分离的功能,您不需要在一个类中包含数十个类。
编辑: 如果 getters 和 setters 的要点是“配置”游戏类(我这样理解你的评论),那么你可能不需要 getters(类访问自己的私有变量而不使用get 方法),并且您可能可以将许多设置器折叠为“组设置器”,这些设置器设置概念上属于在一起的多个变量。
getter 和 setter 的存在往往表明(如果您喜欢小学语言的话,这是一种“气味”)存在设计问题。琐碎的 getter 和 setter 与公共字段几乎没有区别。通常,对数据进行操作的代码将位于不同的类中 - 封装性很差,并且您会期望不熟悉 OO 的程序员这样做。
在某些情况下,getter 和 setter 都很好。但通常来说,同时具有 getter 和 setter 的类型表明存在设计问题。Getters 致力于实现不变性;设置者的工作宗旨是“告诉而不是询问”。不变性和“告诉不要问”都是很好的设计选择,只要它们不以重叠的方式应用即可。
我的观点是 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
已设置。那么,你能不能只重写 setter 然后 并且只有 然后?
def my_value=(value)
@my_value = value
@count++
end
我完全支持漂亮的代码,但我必须承认,浏览我们拥有的大量 Java 类,看到成千上万行除了基本的 getter/setter 之外什么都没有的代码,是丑陋和烦人的。
当我全职使用 C# 进行开发时,我们一直使用公共属性,并且仅在需要时才执行自定义 getter/setter。就像一个魅力一样,它没有破坏任何东西。
一如既往,唯一的答案是:这取决于。如果您是唯一接触代码的人,您可以做任何您喜欢的事情,包括走捷径。
使用 setter 的好处之一是只需在代码中的一个位置执行检查。
您可能需要更密切地关注这些方法实际获取和设置的内容。如果您使用它们来提供对常量值的访问,那么使用常量可能会更好。
这取决于所讨论的编程语言。你的问题是在Java的背景下提出的,似乎getter和setter通常被认为是一件好事。
相比之下,在 Python 世界中,它们通常被认为是不好的风格:他们在代码中添加行,但没有实际添加功能。当 Python 程序员需要时,他们可以使用元编程来捕获对象属性的获取和/或设置。
在 Java 中(至少是我十年前学过的 Java 版本),这是不可能的。因此,在 Java 中,通常最好严格使用 getter 和 setter,这样如果需要,您可以覆盖对变量的访问。
(这并不意味着 Python 一定比 Java 更好,只是不同而已。)
仅供参考:除了本线程中所有出色的答案之外,请记住,在您可以提出支持或反对 getter/setter 的所有原因中,性能并不是其中之一(正如某些人可能认为的那样)。JVM 足够聪明,可以内联简单的 getter/setter(甚至是非final
那些,只要它们实际上没有被覆盖)。
您可能希望用值类替换某些类。这将允许您删除 getter 并避免在内容更改时出现问题。
如果您需要从外部访问字段的各个值,请使用 getter 和/或 setter。如果没有,就不要。切勿使用公共字段。就这么简单!(好吧,从来没有 那 很简单,但这是一个很好的经验法则)。
一般来说,您还应该发现您需要提供的 setter 比 getter 少得多 - 特别是如果您试图使对象不可变 - 这是一件好事(但并不总是最好的选择) - 但即使不是。
几个月前我一直在用 java 编程,我了解到我们应该仅在应用程序需要时才使用 getter 和 setter
玩得开心 :)