Defining 'Tell, Don't Ask'... Well, Almost...
Join the DZone community and get the full member experience.
Join For FreeThe big idea is that object A tells object B what to do and object B tells C as shown in the diagram below:
...and the idea behind this is that you never interrogate the state of
an object in order to make a decision about what to do next - after all,
that’s procedural programming.
To demonstrate this, I’m going to use the proverbial shopping cart
scenario in which the user adds items to the shopping cart and then gets
hold of the items to calculate their total cost.
The item code is very straight forward and looks like this:
public class Item { private final String code; private final Double price; public Item(String code, Double price) { this.code = code; this.price = price; } public String getCode() { return code; } public Double getPrice() { return price; } }
…whilst the flawed ask don’t tell shopping cart looks like this:
public class ShoppingCart { private final List<Item> items; public ShoppingCart() { items = new ArrayList<Item>(); } public void addItem(Item item) { items.add(item); } public List<Item> getAllItems() { return Collections.unmodifiableList(items); } }
All that’s left to do here is to calculate the total code as shown in this unit test:
public class ShoppingCartTest { /** * Test method for {@link tell_dont_ask.ask.ShoppingCart#getAllItems()}. */ @Test public void calculateTotalCost() { ShoppingCart instance = new ShoppingCart(); Item a = new Item("gloves", 23.43); instance.addItem(a); Item b = new Item("hat", 10.99); instance.addItem(b); Item c = new Item("scarf", 5.99); instance.addItem(c); double totalCost = calcTotalCost(instance); assertEquals(40.41, totalCost, 0.0001); } private double calcTotalCost(ShoppingCart instance) { List<Item> items = instance.getAllItems(); double total = 0.0; for (Item item : items) { total += item.getPrice(); } return total; } }
An that should about wrap things up, the test passes and everyone's
happy. Or are they? If you look at the code more closely you’ll see that
in order to calculate the total cost the unit test client has to ask
the shopping cart to give it a list of its items and then the unit test
iterates through them to work out the total. Even though the shopping
cart quite rightly returns an immutable list, it is still giving the
unit test client code access to its internal state, breaking the rules
of encapsulation.
So, this where Tell Don’t Ask comes to the rescue...
public class ShoppingCart { private final List<Item> items; public ShoppingCart() { items = new ArrayList<Item>(); } public void addItem(Item item) { items.add(item); } public double calcTotalCost() { double total = 0.0; for (Item item : items) { total += item.getPrice(); } return total; } }
public class ShoppingCartTest { /** * Test method for {@link tell_dont_ask.ask.ShoppingCart#calculateTotalCost()}. */ @Test public void calculateTotalCost() { ShoppingCart instance = new ShoppingCart(); Item a = new Item("gloves", 23.43); instance.addItem(a); Item b = new Item("hat", 10.99); instance.addItem(b); Item c = new Item("scarf", 5.99); instance.addItem(c); double totalCost = instance.calcTotalCost(); assertEquals(40.41, totalCost, 0.0001); } }
In the tell don’t ask version of this code the JUnit client code tells the shopping cart that it wants its total cost and the shopping cart calculates the value, returning it to the caller.
And that’s my tell don’t ask definition in a nutshell, but is it
the end of the story? I don’t think so, after this it all becomes more
subjective. More on that later.
Published at DZone with permission of Roger Hughes, DZone MVB. See the original article here.
Opinions expressed by DZone contributors are their own.
Comments