The 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.
4 comments:
Nice post.
Just pointing out that we should really be introduce new methods that does not violate the single responsibility principle.
You may update the javadoc on last ShoppingCartTest to reflect the call to calcTotalCost() instead of getAllItems()
Nice example of Tell don't ask btw
Cheers
Javadoc fixed - thanks
The one thing that bothers me is - why do most programmers don't use this technique in their code ? It's far more readable and testable. Also, why so much Java libraries is written in the wrong procedural way, instead of clean OO tell don't ask ?
Post a comment