2

I'm taking a course on design patterns in software engineering and here I'm trying to understand the good and the bad way of design relating to "coupling" and "cohesion".

I could not understand the concept described in the following image. enter image description here The example of code shown in the image is ambiguous to me, so I can't quite clearly get what exactly "Ask, don't tell!" approach mean. Could you please explain?

3
  • it's the difference between turn by turn directions and saying "meet me at my place" Commented Nov 9, 2013 at 20:19
  • 9
    Either I'm confused, or this is usually "tell, don't ask" not "ask, don't tell" Commented Nov 9, 2013 at 20:26
  • 1
    This is due to a cultural difference in the subtle understanding of "ask" and "tell". "Ask" in this idiom does not actually mean a request, but it means an oblique, circuitous way of doing things, which takes more effort compared to the imperative, declarative way.
    – rwong
    Commented Nov 9, 2013 at 21:40

4 Answers 4

9

The first example tells the object in question, "I need these accessors and this data from those accessors". That may be fine and good but it requires that you know how the object accesses those things.

The second example asks the object "Can you do this thing?", and then the object either returns that operation or fails. There is no requirement of internal knowledge of the object, thus facilitating the idea of loose coupling since I can then freely change the underlying object's interal functionality so long as the accessor interface, the "tum.addStudentToLecture(...), remains the same.

1

"Ask" code breaks when its object changes the details of its operation. It has to state the types the object will be returning, it has to make assumptions about the mutability and synchronicity of those returns, and it forces other objects to implement "Ask" code to provide the other details they need to cooperate.

"Tell" code, by contrast, keeps all of types and implementation considerations in the hands of the object that can change them. When they do change, external "Tell" code doesn't have to be revised in order to remain valid.

To make it concrete using your example, let's say that tomorrow, you or a co-worker:

  • Need to make the list of students accessible asynchronously and can't use a SortedList.

  • Need to store students by different keys in multiple containers, so student.getKey() becomes ambiguous or misleading.

  • Need to query the elements of pse2013students by more than a simple key.

If you used the "Ask" code, you need to revise both the tum object and the method that employs it in the example. If you use the "Tell" code, you only needed to make changes to the tum object. It doesn't look like much of a difference when your code base is two lines, but in a non-trivial program, it can become a severe maintainability hazard.

1

In object oriented design, classes have a:

  • a public interface that does not change
  • hidden implementation that may be changed

So if you change the implementation, for example add new functionality, as long as the interface still works the way it did before, you can be assured that all dependent classes will continue to function properly and you don't need to test them.

So in the "bad" example, when the code "tells" the tum object how to add a student to the list. Now the invoking classes is messing around with the internals of how the student list is kept. If the author of the tum object decides to replace:

getStudentList(Lectures.PSE,2013) 

with a new function

getStudentMap(Lectures.PSE, Campus.Main, 2013)

you have to change your code to get it to work the way it did before because of something they did. Upgrading common utility library would potentially become a nightmare be it might break tons of code. Yet the utility library might have some critical security fix so you might have to upgrade it.

In the good "ask" example, the code invokes a function inside the tum object to add a student to the list. The author of the tum object is maintaining this functionality and it is their responsibility not yours. This is a good thing. They are now obliged to keep it working correctly and if it doesn't now they are obliged to fix it, not you.

In addition, there might be other requirements you are not aware of that they might be doing. For example, checking the student records and if this is the very first lecture a student attends, mail them a welcome letter. Your class would have no idea about that (or care) and yet now if that became a requirement you would have to add and maintain that to your code.

0

"Tell, don't ask" works on the premise that an object shouldn't have to be told to do something according to some information about the object itself. Surely the object should know its own state? You shouldn't have to couple code outside of the object to perform some function it could very well do itself. I found the example picture terribly confusing so I'll use another example instead.

Imagine you play a complex game such as World of Warcraft. You are a warlock and you want to use the Fear spell on some other player. The spell should send them running for 8 seconds. If you cast it again that time will be halved due to diminishing returns, and so on. There are many factors deciding if the spell hit should be a success or not.

Now, the Fear spell could be implemented like this:

function castFear(Player player) {
  if (player->isAPaladinAndFearsNothing())
    return false;
  if (player->hasTakenAntiFearPotion())
    return false;
  if (currentPlayer->getStat("shadow_magic_power") < player->getStat("shadow_magic_resistance"))
    return false;

  player->receiveFear();
}

With "tell, don't ask" this code could be simplified drastically:

function castFear(Player player) {
  player->receiveFear(currentPlayer);
}

The Player class could be implemented like this:

class Player {
  public boolean receiveFear(Player castingPlayer) {
    if (this->isAPaladinAndFearsNothing())
      return false;
    if (this->hasTakenAntiFearPotion())
      return false;
    if (castingPlayer->getStat("shadow_magic_power") < this->getStat("shadow_magic_resistance"))
      return false;

    // Get feared
  }
}

Now, both solutions might seem quite similar, but if you think about it, whose responsibility is it to know if the target player can be feared or not? "Tell, don't ask" might appear as a "neat trick", but I've found this style of coding to be one of the cornerstones of OOP. You should not know of whatever complexity is hidden behind that public function and as such should not take action against it either. What if some of your new spells should also Fear your target? You'd have to stay up to date on all the checks you'd have to make, everywhere. If you wanted to implement "Healing Horror" that fears your target and heals you, you could just write:

function castHealingHorror(Player player) {
  currentPlayer->health += 50;
  player->receiveFear(currentPlayer);
}

I'd say this is also a question of which object should perform the checks, the sender or the receiver? Well, think about it. If you were to send a letter to an official containing inappropriate wording, wouldn't your letter be halted somewhere along the way? For all you know someone might read the mail before your recipient does and filter it out as a security measure. Should you code for this as the sender? All you should do is take your prepared Letter object, give it to the mailman and that's it. Maybe it doesn't even reach the destination post office due to some inconvenience.

Expose yourself to as little complexity and information as possible and let each component deal with its own business rules. It's then easier to combine them into a fully encapsulated system.

Not the answer you're looking for? Browse other questions tagged or ask your own question.