Yet, another post about tell don’t ask

Tell, don’t ask is a useful key concept when talking about object-oriented programming, it consists in to send messages to object (whenever possible) telling objects what you want and that we shouldn’t ask questions to it about its state and make decisions for him, it bypasses encapsulation.

In a more abstract mindset, every object needs to trust that other objects can make decisions alone, without his help.

In a more abstract mindset, every object needs to trust that other objects can make decisions alone, without his help.

If you’re treating objects like structs you’re wrong, the key difference between they is that structs stores only data and was made to represent a record, and this is one of the key difference betwween procedural and object oriented code, objects have behavior.

Stop treating objects like C structures

Structures vs Objects

Defining a simple People struct in C lang and asking for its name if not empty:

struct People {
    int age;
    int weight;
    char name[50];
};
struct People ronald = { name: "", age: 42, weight: 140};

if (strcmp(ronald.name, "") != 0) {
    printf("Person Name: %s\n", ronald.name);
}

This C code in Ruby will look something like this in Ruby:

class People
  attr_reader :name

  def initialize(age:, weight:, name:)
    @age = age
    @weight = weight
    @name = name
  end
end

people = People.new(age: 45, weight: 140, name: "Ronald")

puts "People name: #{people.name}" unless people.name.empty?

The error is treating only like a record, objects have behavior too.

class People
  def initialize(age:, weight:, name:)
    @age = age
    @weight = weight
    @name = name
  end

  def name
    "People name: #{@name}" unless @name.empty?
  end
end

people = People.new(age: 42, weight: 140, name: "Ronald")

puts people.name

Ok… This is just a mediocre example, we only extracted a simple logic, but it was good to see the difference between structs (only data) and objects (behavior/state).

Another Example

Related Posts

Ok, let’s go to another example more real, let’s imagine a simple system that has Books and Stock and we want to store books at stock and print every book of stock:

class Book
  attr_reader :name, :author, :year

  def initialize(name, author, year)
    @name = name
    @author = author
    @year = year
  end
end

class Stock
  attr_reader :capacity, :maximum_capacity
  attr_accessor :books

  def initialize(books: [], capacity: 0, maximum_capacity: 10)
    @books = books
    @capacity = capacity
    @maximum_capacity = maximum_capacity
  end
end

def add_books(book, stock)
  if stock.capacity < stock.maximum_capacity
    stock.books << book
  end
end

def print_books(stock)
  stock.books.each do |book|
    puts "Name: #{book.name}, Author: #{book.author}"
  end
end

book1 = Book.new("Practical Object-Oriented Design in Ruby","Sandi Metz","2012")
book2 = Book.new("Ruby Under a Microscope","Pat Shaughnessy","2013")

stock = Stock.new

add_books(book1, stock)
add_books(book2, stock)

print_books(stock)

This code works good but what’s the problem? This code is so procedural, let’s refactor to become more OOP.

Starting from add_books, it’s asking a lot of information about object(stock) state for making decisions, this is wrong and the logic or add books to stock and check about stock capacity needs to belong to a stock object, with Tell don’t ask thinking this logic would be written like this:

class Stock
  attr_reader :quantity, :maximum_quantity
  attr_accessor :books

  def initialize(books: [], quantity: 0, maximum_quantity: 10)
    @books = books
    @quantity = quantity
    @maximum_quantity = maximum_quantity
  end

  def <<(book)
    books << book unless crowded
  end

  def crowded
    quantity == maximum_quantity
  end
end

Now if we need to one book to stock we just need to write:

stock << book1

We don’t need to ask questions about state and then add, all this logic is encapsulated at an object, much better right?

print_books need to be refactored too, let’s make this logic more OOP, first of all, we can create a method at Stock to print all books and their information.

class Stock
  ...

  def print_books
    books.each do |book|
      puts "Name: #{book.name}, Author: #{book.author}"
    end
  end
end
class Book
  ...

  def full_description
    "Name: #{name}, Author: #{author}, Year: #{year}"
  end
end

Now we refactor again our print_books for just call full_description on each book.

class Stock
  ...

  def print_books
    books.each do |book|
      puts book.full_description
    end
  end
end

Much better our code now, right? If you got lost you can check full code here, or ask me on twitter/email.

Comparing two codes

On the first version of the code, objects was asking for state like this image:

Objects asking for other object state

Now our objects are telling what want:

Objects telling what want

Luckily on internet we’ve hundreds of examples in many languages, you should search for it after read this post.

“The distinction between a message that asks for what the sender wants and a message that tells the receiver how to behave may seem subtle but the consequences are significant. Understanding this difference is a key part of creating reusable classes with well-defined public interfaces”

Final thoughts

This isn’t easy get started thinking like this and correcting your errors, but you need to get used to not ask your object about his state and tell him what you want every that is possible, at the beginning will be hard to think like this (it’s have been difficult for me), but with time you’ll master this skill.

Thanks all for read this post, don’t forget to follow me on twitter, any questions you can send me an email, have good days.

Useful links

Follow my blog to get notified every new post: