It used to be fairly common for people to call a getter, do some calculation on it, then call a setter with the result. This is a clear sign your calculation actually belongs to the class you called the getter on. “Tell, don’t ask” was coined to remind people to be on the lookout for that anti-pattern, and it worked so well that now some people think that part is obvious
(source: Karl Bielefeldt’s answer)
This is by far not the first time I hear a piece of such advice.
Yet, looking at the game I’m trying to make, I simply can’t seem to see how to bring this code to conformance with this advice. Admittedly, this anti-pattern is rampant there.
This is the current design: Monsters can be under
Effects and can have
Actions queued for execution. Monsters are instances of a class specific to their species, which inherits from the
Species abstract class – which in turn holds state such as their current HP, stamina, a list of
Effects they are under or a list of
Actions that are queued for execution.
Let me put here a few examples why it is so hard for me to conform to this advice. For example, let’s pay a closer look on the
Action. Seems straightforward enough: total attack power is computed as
Attack.power * attacker.power (
attacker is a monster) and this value is substracted from the defender’s current HP. But now, where to put this logic? To conform with this piece of advice we would have to have a
makeAttack method on the attacker, which passes its current
power to the attack, which then multiplies this value by its own power and passes it to the defender’s
beAttacked method, which then substracts this amount from its own HP. And, indeed, this is how I started this.
However, it turns out, this is only the simplest case. The
Attack on its own can be under
Effects. Soon, as the complexity of the game rules was growing, I was finding myself making more and more fields public and moving more and more logic to the effects and actions themselves and away from whatever is under these effects and whatever these actions are supposed to affect.
Consider these requirements:
- If an
Attack is of type
Fire AND it penetrates the defender’s
shield then a
Burn debuff is applied to the defender whose strength is proportional to the amount of fire damage that has passed through the shield.
- This necessites either querying the defender stats after the damage by the
Attack or polluting the defender’s
beAttacked method with additional arguments and logic or having an
applyBurn method on the defender.
- If the defender is blocking (is under a
Block effect) he mitigates a portion of the incoming damage at the cost of some of his
Stamina (he can’t, therefore, mitigate more damage than he has stamina for). How much damage is exactly mitigated depends on the ratio of both monsters’
- Again: put this logic in the
Block buff, which then has to query the state of both monsters AND the power of the attack? Or pollute other classes with unecessary methods?
- If the defender is under a
Dodge buff, he has a chance of avoiding the damage completely, depending on both monsters’
- Attacks that have the
AreaOfEffect field set to
True cannot be dodged at all…
- The battle arena itself can be under effects – for example,
Conflagration. But- there is a combo – if both effects are in play, thy are both removed and a third effect,
Firenado is placed, which is more powerful than both
Conflagration at the same time.
Conflagration is being placed it has to ask the battlefield if there is a
Hurricane already and remove it if necessary…
Force attacks ignore some portion of shield completely,
Pierce ones are instead more powerful to the extend they are blocked by shield
- Make it work with the blocking semantics…
I could go on and on and on. The bottom line is: the only alternative to tell, don’t ask seems to be… For each subclass of
Effect that can affect a monster, make a method on the monster. For each subclass of
Effect that can affect an
Action, make a method on that action. For each subclass of
Action that can target a monster, make a method on that monster.
I don’t like this. Aside from the clutter such a solution would introduce, this kind of defeats the purpose of having separate
Action subclasses. Logic that is semantically theirs would, in the end, be moved to methods of other classes! So, AFAIK, tell, don’t ask is supposed to precisely avoid this what it would now introduce! If I have, say, 15 different types of
Effects, then it makes sense to enable these
Effects to simply operate on instances of
IMonster whose requirements are kept simple (
IMonster is supposed to have HP, stamina, etc, but not necessarily provide a
burnMe method) rather than instances of
IBurnableMonster which kind of makes little sense because every monster is burnable.
This also seems more elegant to me insofar as modifying the game’s rules (adding a new effect or something like that) is supposed to boil down to only adding a new
Effect subclass and/or making changes to one
Effect subclass rather than also considering all classes this
Effect would affect. (OK I’m lying here: the practice shows this is not the case, however, this is what I – ideally – wanted to achieve through this design).
So… currently I have a lot of getters and a lot of public fields. Which, I suppose, is a little tragedy in the eyes of any experienced engineer. As the number of special cases was growing, I even removed the
beAttacked methods from the monster and instead put this logic in the
However, now that I described the “architecture”, how should this be handled in a more right-handed way?