Using getter/setter vs “tell, don't ask”?

2019-03-17 06:10发布

问题:

Tell, don't ask principle here is often pasted to me when I use getters or setters, and people tell me not to use them. The site clearly explains what I should and what I shouldn't do, but it doesn't really explain WHY I should tell, instead of asking. I find using getters and setters much more efficient, and I can do more with them.


Imagine a class Warrior with attributes health and armor:

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

Now someone attacks my warrior with a special attack that reduces his armor for 5 seconds. Using setter's it would be like this:

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

And with tell, don't ask principle it would look like this (correct me if i'm wrong):

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

Now the second one obviously looks better, but I can't find the real benefits of this. You still need the same amount of methods (increase/decrease vs set/get) and you lose the benefit of asking if you ever need to ask. For example, how would you set warriors health to 100? How do you figure out whether you should use heal or hurt, and how much health you need to heal or hurt?

Also, I see setters and getters being used by some of the best programmers in the world. Most APIs use it, and it's being used in the std lib all the time:

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

And if I have to decide whether to believe people here linking me one article about telling, not asking, or to believe 90% of the large companies (apple, microsoft, android, most of the games, etc. etc.) who have successfully made a lot of money and working programs, it's kinda hard for me to understand why would tell, don't ask be a good principle.

Why should I use it (should I?) when everything seems easier with getters and setters?

回答1:

You still need the same amount of methods (increase/decrease vs set/get) and you lose the benefit of asking if you ever need to ask.

You got it wrong. The point is to replace the getVariable and setVariable with a meaningful operation: inflictDamage, for example. Replacing getVariable with increaseVariable just gives you different more obscure names for the getter and setter.

Where does this matter. For example, you don't need to provide a setter/getter to track the armor and health differently, a single inflictDamage can be processed by the class by trying to block (and damaging the shield in the process) and then taking damage on the character if the shield is not sufficient or your algorithm demands it. At the same time you can add more complex logic in a single place.

Add a magic shield that will temporarily increase the damage caused by your weapons for a short time when taking damage, for example. If you have getter/setters all attackers need to see if you have such an item, then apply the same logic in multiple places to hopefully get to the same result. In the tell approach attackers still need to just figure out how much damage they do, and tell it to your character. The character can then figure out how the damage is spread across the items, and whether it affects the character in any other way.

Complicate the game and add fire weapons, then you can have inflictFireDamage (or pass the fire damage as a different argument to the inflictDamage function). The Warrior can figure out whether she is affected by a fire resistance spell and ignore the fire damage, rather than having all other objects in the program try to figure out how their action is going to affect the others.



回答2:

Well, if that's so, why bother with getters and setters after all? You can just have public fields.

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

The reason is simple here. Encapsulation. If you have setters and getters, it's no better than public field. You don't create a struct here. You create a proper member of your program with defined semantics.

Quoting the article:

The biggest danger here is that by asking for data from an object, you are only getting data. You’re not getting an object—not in the large sense. Even if the thing you received from a query is an object structurally (e.g., a String) it is no longer an object semantically. It no longer has any association with its owner object. Just because you got a string whose contents was “RED”, you can’t ask the string what that means. Is it the owners last name? The color of the car? The current condition of the tachometer? An object knows these things, data does not.

The article here suggests here that "tell, don't ask" is better here because you can't do things that make no sense.

target->setHealth(target->getArmor() - m_damage);

It doesn't make sense here, because the armor has nothing in relation to health.

Also, you got it wrong with std lib here. Getters and setters are only used in std::complex and that's because of language lacking functionality (C++ hadn't had references then). It's the opposite, actually. C++ standard library encourages usage of algorithms, to tell the things to do on containers.

std::for_each(begin(v), end(v), my_func);
std::copy(begin(v), end(v), begin(u));


回答3:

One reason that comes to mind is the ability to decide where you want the control to be.

For example, with your setter/getter example, the caller can change the Warrior's health arbitrarily. At best, your setter might enforce maximum and minimum values to ensure the health remains valid. But if you use the "tell" form you can enforce additional rules. You might not allow more than a certain amount of damage or healing at once, and so on.

Using this form gives you much greater control over the Warrior's interface: you can define the operations that are permitted, and you can change their implementation without having to rewrite all the code that calls them.



回答4:

At my point of view, both codes do the same thing. The difference is in the expressivity of each one. The first one (setters anad getters) can be more expressive than the second one (tell, don' ask).

It's true that, when you ask, you are going to make a decision. But it not happens in most part of times. Sometimes you just want to know or set some value of the object, and this is not possible with tell, don't ask.

Of course, when you create a program, it's important to define the responsabilities of an object and make sure that these responsabilities remains only inside the object, letting the logic of your application out of it. This we already know, but if you need ask to make a decision that's not a responsability of your object, how do you make it with tell, don't ask?

Actually, getters and setters prevails, but it's common to see the idea of tell, don't ask together with it. In other words, some APIs has getters and setters and also the methods of the tell, don't ask idea.