Tell, Don’t Ask – Why It’s Good Object-Oriented Practice

cobject-orientedruby

This blogpost was posted on Hacker News with several upvotes. Coming from C++, most of these examples seem to go against what I've been taught.

Such as example #2:

Bad:

def check_for_overheating(system_monitor)
  if system_monitor.temperature > 100
    system_monitor.sound_alarms
  end
end

versus good:

system_monitor.check_for_overheating

class SystemMonitor
  def check_for_overheating
    if temperature > 100
      sound_alarms
    end
  end
end

The advice in C++ is that you should prefer free functions instead of member functions as they increase encapsulation. Both of these are identical semantically, so why prefer the choice that has access to more state?

Example 4:

Bad:

def street_name(user)
  if user.address
    user.address.street_name
  else
    'No street name on file'
  end
end

versus good:

def street_name(user)
  user.address.street_name
end

class User
  def address
    @address || NullAddress.new
  end
end

class NullAddress
  def street_name
    'No street name on file'
  end
end

Why is it the responsibility of User to format an unrelated error string? What if I want to do something besides print 'No street name on file' if it has no street? What if the street is named the same thing?


Could someone enlighten me on the "Tell, Don't Ask" advantages and rationale? I am not looking for which is better, but instead trying to understand the author's viewpoint.

Best Answer

Asking the object about its state, and then calling methods on that object based on decisions made outside of the object, means that the object is now a leaky abstraction; some of its behavior is located outside of the object, and internal state is exposed (perhaps unnecessarily) to the outside world.

You should endeavor to tell objects what you want them to do; do not ask them questions about their state, make a decision, and then tell them what to do.

The problem is that, as the caller, you should not be making decisions based on the state of the called object that result in you then changing the state of the object. The logic you are implementing is probably the called object’s responsibility, not yours. For you to make decisions outside the object violates its encapsulation.

Sure, you may say, that’s obvious. I’d never write code like that. Still, it’s very easy to get lulled into examining some referenced object and then calling different methods based on the results. But that may not be the best way to go about doing it. Tell the object what you want. Let it figure out how to do it. Think declaratively instead of procedurally!

It is easier to stay out of this trap if you start by designing classes based on their responsibilities; you can then progress naturally to specifying commands that the class may execute, as opposed to queries that inform you as to the state of the object.

http://pragprog.com/articles/tell-dont-ask

Related Topic