使用getter/setter方法还是"告诉,不要询问"的方式?

30

当我使用getter或setter时,经常会有人告诉我不要使用它们,并向我贴上“告诉,不要询问原则”的标签。这个网站清楚地解释了我应该做什么,以及我不应该做什么,但它并没有真正解释为什么我应该“告诉”而不是“询问”。我发现使用getter和setter更高效,而且我可以做更多的事情。


想象一个具有属性healtharmor战士类:

class Warrior {
    unsigned int m_health;
    unsigned int m_armor;
};

现在有人用一种特殊攻击攻击我的战士,这会使他的护甲降低5秒钟。使用setter,就像这样:

void Attacker::attack(Warrior *target)
{
    target->setHealth(target->getHealth() - m_damage);
    target->setArmor(target->getArmor() - 20);
    // wait 5 seconds
    target->setArmor(target->getArmor() + 20);
}

而使用“告诉,不要询问”的原则,它看起来会像这样(如果我错了,请纠正我):

void Attacker::attack(Warrior *target)
{
    target->hurt(m_damage);
    target->reduceArmor(20);
    // wait 5 seconds
    target->increaseArmor(20);
}

第二种方法显然更好,但我找不到其真正的好处。 你仍需要相同数量的方法(increase/decreaseset/get),并且你失去了询问时的好处。 例如,如何将战士的血量设置为100? 你怎么知道是否应该使用heal还是hurt,以及需要治疗或伤害多少生命值?

此外,我看到一些世界上最好的程序员正在使用setter和getter。 大多数API都在使用它,并且它一直在std lib中使用:

for (i = 0; i < vector.size(); i++) {
    my_func(i);
}
// vs.
vector.execForElements(my_func);

如果我必须决定是否相信这里的人链接给我的一篇关于“告诉,不是询问”的文章,或者相信90%的大公司(苹果、微软、安卓、大多数游戏等等)已经成功地赚了很多钱并拥有工作程序,那么我很难理解为什么“告诉,不是询问”会是一个好原则。

当一切似乎都可以通过使用getter和setter更轻松时,我为什么应该使用它呢?


与其使用单个的血量(hp)或护甲(armor),你可以使用 hp_reduction 列表和 armor_reduction 列表,这样当多个敌人同时攻击同一受害者时,它们不会因为多个线程访问同一变量造成健康和护甲的问题。在一个线程中等待 5 秒钟行吗?你如何确保它确切地等待了 5 秒钟?你可能也需要任务调度器。 - huseyin tugrul buyukisik
7
谢谢,但我并不打算编写一个可执行的游戏。这只是一个简单的例子。 - Skamah One
然后你可以使用最简单的赋值操作。Warrior.HP-=5; - huseyin tugrul buyukisik
4个回答

32
你仍然需要同样数量的方法(增加/减少与设置/获取),并失去了询问是否需要询问的好处。 你理解错了,重点是用有意义的操作替换getVariable和setVariable:例如inflictDamage。将getVariable替换为increaseVariable只会为getter和setter提供不同而更晦涩的名称。 这在哪里很重要。例如,您不需要提供一个setter / getter来单独跟踪护甲和生命值,一个单一的inflictDamage可以通过尝试阻止(并在此过程中损坏盾牌)来由类来处理,并且如果盾牌不足或您的算法要求,则对角色造成伤害。同时,您可以在单个位置添加更复杂的逻辑。
例如添加一个神奇的护盾,当受到伤害时,这个护盾会暂时增加武器造成的伤害时间,那么如果您具有getter / setter,则所有攻击者都需要查看您是否拥有这样的物品,然后在多个地方应用相同的逻辑,以期望达到相同的结果。在“ 告诉”方法中,攻击者只需要弄清楚他们造成的伤害量并告诉您的角色即可。然后,角色可以计算伤害如何分布在物品上,以及它是否以其他方式影响角色。
增加火器并复杂化游戏,那么您可以使用inflictFireDamage(或将火伤害作为不同的参数传递给inflictDamage函数)。战士可以弄清楚她是否受到火焰抵抗法术的影响并忽略火焰伤害,而不是让程序中的所有其他对象都尝试弄清楚他们的行动将如何影响其他对象。

3
这是唯一一个真正解释“告诉,别问”的好处的补充答案,而不仅仅是说“当然,它可以让你更好地封装”。你完全值得被采纳,感谢你的回答! :) - Skamah One

2

那么,既然如此,为什么还要使用getters和setters呢?你可以直接使用公共字段。

void Attacker::attack(Warrior *target)
{
    target->health -= m_damage;
    target->armor -= 20;
    // wait 5 seconds
    target->armor += 20;
}

原因很简单,就是封装。如果你有setter和getter,那么它与公共字段没有什么区别。你在这里不是创建一个结构体,而是创建了程序的一个适当成员,具有定义明确的语义。
引用文章:
最大的危险在于通过从对象请求数据,您只能获取数据。您没有获取到一个对象-不是从大的意义上讲。即使您从查询中获得的东西在结构上是一个对象(例如,一个字符串),它在语义上也不再是一个对象。它不再与其所有者对象有任何关联。即使您获得了一个内容为“RED”的字符串,您也不能问该字符串是什么意思。它是所有者的姓氏吗?汽车的颜色?转速表的当前状态?对象知道这些事情,数据则不知道。
本文建议在这里使用“告诉,不要问”,因为您不能做没有意义的事情。
target->setHealth(target->getArmor() - m_damage);

这里并没有意义,因为盔甲和健康没有关系。
此外,在这里你的std lib理解有误。Getters和setters仅在std::complex中使用,这是由于语言缺乏功能(当时C++还没有引用)。实际上相反,C++标准库鼓励在容器上使用算法来告诉它们要做什么。
std::for_each(begin(v), end(v), my_func);
std::copy(begin(v), end(v), begin(u));

我更喜欢将 health 更改为保持 >= 0,而无需让我个人担心它。 - chris
2
不要影响他的健康。告诉他你踢了他的屁股有多狠,让他能够相应地管理自己的健康。 :) - Pixelchemist
1
获取器和设置器比公共字段要好得多,因为您可以根据更改更新内部并验证更改。您甚至可以在客户端不知道的情况下修改底层表示。 - Jack Aidley
1
如果您需要在每次更改健康状态时记录日志怎么办?如果健康状况下降到原始水平的一半以下,您需要将状态注册为“流血”。此外,您对底层表示完全错误。公共字段严格劣于getter / setter。 - Jack Aidley
1
那是一种过于复杂且容易出错的壮观方式。你知道,有时候,getter/setter 是正确的方法。有时候你真正需要一个可以获取和设置的值,而 getter/setter 仍然是更好的方法。在寻找更好的解决方案时,不要忽视 getter/setter 的好处。 - Jack Aidley
显示剩余5条评论

1
一种显而易见的原因是可以决定控制权所在。例如,在您的setter/getter示例中,调用者可以任意更改战士的健康状况。最好的情况下,您的setter可能会强制实施最大和最小值,以确保健康保持有效。但如果使用“告诉”形式,则可以强制执行其他规则。您可能不允许一次造成超过一定量的伤害或治愈等等。使用这种形式可以更好地控制战士的接口:您可以定义允许的操作,并且可以在不必重写所有调用它们的代码的情况下更改其实现。

非作弊的生命值数值也可以在攻击函数中保留。 - huseyin tugrul buyukisik
你也可以在 setter 中完成大部分的操作,是吗? - user2032433

0

在我看来,这两个代码做的事情是相同的。区别在于每个代码的表达力不同。第一个代码(setters and getters)比第二个代码(tell, don't ask)更具表现力。

当你询问时,确实会做出决定。但这并不是大多数情况下发生的。有时候,您只是想知道或设置对象的某些值,并且这在 tell, don't ask 中是不可能的。

当然,创建程序时,定义对象的责任并确保这些责任仅留在对象内部,让应用程序的逻辑排除在外是很重要的。我们已经知道这一点,但如果您需要询问以做出不属于对象责任范围内的决策,您如何使用tell, don't ask进行操作?

实际上,getters 和 setters更为普遍,但通常与tell, don't ask思想一起使用。换句话说,一些API既有getter和setter,也有tell, don't ask思想的方法。


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接