Tell, Don't Ask Principle Explained with Java Examples
Introduction
This tutorial explains the Tell, Don't Ask principle with Java examples. We will start with what the principle says and where it comes from. We will then build the same leave-booking feature twice - once by asking an object for its data and deciding outside it, and once by telling the object what to do - and see the "ask" version end up with a negative leave balance. Lastly, we will look at when asking is perfectly fine, and how the principle relates to the Law of Demeter and Command-Query Separation.What is Tell, Don't Ask
Tell, Don't Ask says: rather than asking an object for its data and then making decisions about that data, tell the object what you want done, and let it decide how. The idea is usually traced to Alec Sharp's book Smalltalk by Example (1997): "Procedural code gets information then makes decisions. Object-oriented code tells objects to do things." Andy Hunt and Dave Thomas later made the name popular.The principle is really about where a rule lives. When callers read an object's state, decide, and write the result back, the rule lives in the callers - in as many copies as there are callers. When the object makes the decision itself, the rule lives in one place, next to the data it protects. That is encapsulationClick to read: encapsulation in its full sense: hiding not only the fields, but also the decisions about them.
Example - asking versus telling
An employee has a leave balance, and leave can only be booked if enough days are left. In the "ask" version,EmployeeData has a getter and a setter, and two callers - a leave screen and a bulk import job - each implement the booking. In the "tell" version, Employee has a takeLeave() method and nothing else can change the balance.
Java code comparing the ask style and the tell style for booking leave
//TellDontAskExample.java
public class TellDontAskExample {
// ---------- Ask style: data with a getter and a setter ----------
static class EmployeeData {
private final String name;
private int leaveBalance;
EmployeeData(String name, int leaveBalance) {
this.name = name;
this.leaveBalance = leaveBalance;
}
String getName() { return name; }
int getLeaveBalance() { return leaveBalance; }
void setLeaveBalance(int leaveBalance) { this.leaveBalance = leaveBalance; }
}
// Caller 1 remembers the rule
static class LeaveScreen {
void book(EmployeeData e, int days) {
if (e.getLeaveBalance() >= days) {
e.setLeaveBalance(e.getLeaveBalance() - days);
System.out.println("LeaveScreen booked " + days + " days for " + e.getName());
} else {
System.out.println("LeaveScreen refused: not enough leave");
}
}
}
// Caller 2, written later by someone else, forgets it
static class BulkImport {
void book(EmployeeData e, int days) {
e.setLeaveBalance(e.getLeaveBalance() - days);
System.out.println("BulkImport booked " + days + " days for " + e.getName());
}
}
// ---------- Tell style: the object enforces its own rule ----------
static class Employee {
private final String name;
private int leaveBalance;
Employee(String name, int leaveBalance) {
this.name = name;
this.leaveBalance = leaveBalance;
}
void takeLeave(int days) {
if (days <= 0) {
throw new IllegalArgumentException("Days must be positive: " + days);
}
if (days > leaveBalance) {
throw new IllegalStateException(name + " has " + leaveBalance
+ " days left, cannot take " + days);
}
leaveBalance -= days;
}
int leaveBalance() { return leaveBalance; } // a query for display is fine
}
public static void main(String[] args) {
EmployeeData harry = new EmployeeData("Harry Major", 10);
new LeaveScreen().book(harry, 7);
new BulkImport().book(harry, 7);
System.out.println("Harry's balance, ask style : " + harry.getLeaveBalance());
Employee nancy = new Employee("Nancy Smith", 10);
nancy.takeLeave(7);
try {
nancy.takeLeave(7);
} catch (IllegalStateException e) {
System.out.println("Refused: " + e.getMessage());
}
System.out.println("Nancy's balance, tell style: " + nancy.leaveBalance());
}
}
LeaveScreen booked 7 days for Harry Major BulkImport booked 7 days for Harry Major Harry's balance, ask style : -4 Refused: Nancy Smith has 3 days left, cannot take 7 Nancy's balance, tell style: 3
Explanation of the code
EmployeeDataexposes its balance for reading and writing, so the rule "you cannot book more leave than you have" has to be written by each caller.LeaveScreenwrote it;BulkImportdid not, and Harry ended up with a negative balance.Employeehas no setter. The only way to change the balance istakeLeave(), which checks the rule every time - no caller can forget it, and no caller can get it slightly wrong.- When the request is refused, the exception message says why. The decision and its explanation both come from the object that has the information.
leaveBalance()is still available, because the screen needs to show the balance. Tell, Don't Ask does not forbid reading data; it forbids reading data in order to make a decision that the object should make itself.- If the rule changes - for example, allowing up to 2 days of negative balance for senior staff - only
takeLeave()changes.
Signs that you are asking instead of telling
- Get, decide, set: code that calls a getter, tests the value, and calls a setter on the same object.
- The same
ifin several places: two classes checkgetStatus() == ACTIVEbefore doing something to an object. - Feature envy: a method that uses another object's data more than its own. It usually wants to move into that object.
- Anemic domain model: domain classes that are only fields, getters and setters, with all the logic in "service" classes. Martin Fowler named this anti-pattern in 2003.
When asking is the right choice
Tell, Don't Ask is a guide, not a ban on getters -- Displaying and reporting: a screen, report or API response needs the data itself. Asking for it to show it is fine.
- Decisions that belong to the caller: choosing a colour for an overdue invoice is a presentation decision. The invoice should report that it is overdue; it should not know about colours.
- Data carriers: records and DTOs exist to move data between layers. They have no rules of their own to protect.
- Combining several objects: when a decision needs data from many objects, no single object can make it. A coordinating method asks each for what it needs, and tells each what to do.
Related principles
- Law of Demeter: asking often comes as a chain of getters to reach the data. Telling the nearest object to do the work removes the chain - see the Law of Demeter tutorialClick to read tutorial: Law of Demeter in Java Explained with Examples.
- Command-Query Separation:
takeLeave()is a command that changes state, andleaveBalance()is a query that only reports it. Keeping the two kinds of methods apart is the subject of Command-Query SeparationClick to read tutorial: Command-Query Separation (CQS) with Java Examples. - Design by Contract: the checks inside
takeLeave()are its preconditions. Design by ContractClick to read tutorial: Design by Contract in Java with Examples makes them an explicit part of the method's specification.
Summary
In this tutorial we first saw that Tell, Don't Ask means telling an object what to do rather than reading its data and deciding outside it, so that each rule lives in one place next to its data. Next we booked leave in two styles: with a getter and setter, a second caller forgot the rule and produced a negative balance; with atakeLeave() method, the rule could not be bypassed. We then listed the signs of asking instead of telling. We then saw when asking is the right choice - display, caller-owned decisions, data carriers and coordination. Lastly, we related the principle to the Law of Demeter, Command-Query Separation and Design by Contract.Programming Principles Tutorials
Law of Demeter in Java Explained with ExamplesClick to read tutorial: Law of Demeter in Java Explained with Examples Separation of Concerns Explained with Java ExamplesClick to read tutorial: Separation of Concerns Explained with Java Examples SOLID Principles in Java: Overview with ExamplesClick to read tutorial: SOLID Principles in Java: Overview with Examples Composition over Inheritance in Java with ExamplesClick to read tutorial: Composition over Inheritance in Java with Examples Coupling and Cohesion Explained with Java ExamplesClick to read tutorial: Coupling and Cohesion Explained with Java Examples DRY, KISS and YAGNI Principles with Java ExamplesClick to read tutorial: DRY, KISS and YAGNI Principles with Java Examples Dependency Injection vs Dependency Inversion vs IoCClick to read tutorial: Dependency Injection vs Dependency Inversion vs IoC
Law of Demeter in Java Explained with ExamplesClick to read tutorial: Law of Demeter in Java Explained with Examples Separation of Concerns Explained with Java ExamplesClick to read tutorial: Separation of Concerns Explained with Java Examples SOLID Principles in Java: Overview with ExamplesClick to read tutorial: SOLID Principles in Java: Overview with Examples Composition over Inheritance in Java with ExamplesClick to read tutorial: Composition over Inheritance in Java with Examples Coupling and Cohesion Explained with Java ExamplesClick to read tutorial: Coupling and Cohesion Explained with Java Examples DRY, KISS and YAGNI Principles with Java ExamplesClick to read tutorial: DRY, KISS and YAGNI Principles with Java Examples Dependency Injection vs Dependency Inversion vs IoCClick to read tutorial: Dependency Injection vs Dependency Inversion vs IoC