Hello! Welcome to your next lesson in the Fundamental Design Principles module.
In our previous lesson, we explored the Law of Demeter, which guides us to "only talk to our immediate friends" to reduce coupling. As we saw, refactoring code to follow this law often involves asking an object to perform a task for us, rather than reaching through it to get data and acting on that data ourselves.
Today, we'll formalize this idea by focusing on the "Tell, Don't Ask" principle. This principle is a cornerstone of object-oriented design and a natural extension of the Law of Demeter. It encourages us to build objects that are autonomous and responsible, not just simple data containers. Mastering this principle is key to demonstrating a mature understanding of low-level design in an interview setting.
Our learning outcome for this lesson is to apply the 'Tell, Don't Ask' principle to improve encapsulation and object autonomy.
1. The Core Idea: Telling vs. Asking
The "Tell, Don't Ask" principle states that instead of asking an object for its internal state and then making decisions based on that state in the client code, you should tell the object what you want it to do. The object itself should then use its own internal state to decide how to handle the command.
Let's start with a short video that explains this core concept with a simple, memorable analogy.
Tell Don't Ask | Code Walks 011
The video 'Tell Don't Ask' by Christopher Okhravi provides a clear conceptual introduction to the principle.
Watch the video from the beginning to 05:00. Focus on: The definition of 'asking' vs. 'telling'. The 'hungry animal' example, which illustrates how logic moves from the client into the object itself. The idea that the object should have enough information to make decisions on its own behalf.
As the video explains, the "Ask" style is procedural:
- Get data from Object A.
- Get data from Object B.
- Make a decision and perform some logic.
The "Tell" style is object-oriented:
- Tell Object A to do something.
- Object A uses its own data (and potentially data from collaborators) to perform the logic internally.
This shift in thinking leads to objects that are more than just passive data holders; they become active participants with well-defined behaviors.
2. The Problem with "Asking": Anemic Objects and Scattered Logic
When we violate the "Tell, Don't Ask" principle, we tend to create what are known as Anemic Domain Models. These are classes that consist almost entirely of public getters and setters with little to no business logic. All the logic that should belong to these objects is instead handled by external "service" or "manager" classes.
You've likely encountered this pattern in enterprise applications, where entities or DTOs are often just property bags. While this can seem straightforward, it has significant downsides:
- Encapsulation is broken: The object's internal state is exposed for any client to manipulate.
- Logic is scattered: Business rules are spread across multiple service classes instead of being consolidated with the data they operate on.
- Code duplication: The same
ifchecks and state manipulation logic often appear in multiple places. - High maintenance cost: A change to a business rule might require hunting down and modifying code in many different files, which is error-prone.
The following video demonstrates this problem with a practical C# example that is directly translatable to Java. It shows how a seemingly simple ToDoItem class leads to complex, fragile client code.
The video 'Tell, Don't Ask!' by Ardalis clearly shows the negative consequences of the 'Ask' style and how client code becomes bloated with responsibility.
Watch from the beginning to 04:10. Pay close attention to: The initial ToDoItem class, which is a classic anemic object (a 'property bag'). How the client code is responsible for everything: setting audit fields (CreatedBy, UpdatedBy), checking for existing state (if (!item.IsComplete)), and managing multiple properties at once. The presenter's explanation of why this is a 'recipe for disaster'.
The key issue highlighted in the video is that the developer using the ToDoItem has to remember all the rules associated with it. This is unreliable and violates the core OOP goal of bundling data with the behavior that acts upon it.
3. Refactoring from "Ask" to "Tell"
The solution is to move the logic from the client code into the object itself, creating methods that express clear, imperative commands.
Let's continue with the same ToDoItem example and see how it can be refactored to follow the "Tell, Don't Ask" principle.
This next segment from the same Ardalis video shows the refactoring process, transforming the anemic object into a rich, well-encapsulated entity.
Watch from 04:10 to the end. Observe how the design is improved by: Creating specific methods for state transitions, like updateName() and markComplete(). Making setters private to prevent uncontrolled external modification. Encapsulating the 'don't mark complete if already complete' logic inside the markComplete method. Using a static factory method Create() to ensure objects are always created in a valid state.
The final result is a ToDoItem class that is much more robust. The client code is simplified to just this:
// Creation
ToDoItem item = ToDoItem.create("Record YouTube video", userId);
// Update
item.updateName("Record 'Tell, Don't Ask' video", userId);
// Mark Complete
item.markComplete(userId, DateTime.now());
Notice how the client code is now telling the item what to do (create, updateName, markComplete). It no longer needs to ask about the item's internal state before acting. All the complex logic and business rules are safely encapsulated within the ToDoItem class.
Here's another great illustration using a farm simulation example.

4. Benefits and When It's Okay to "Ask"
Adhering to "Tell, Don't Ask" provides numerous benefits that lead to higher-quality software.
Tell, Don't Ask — Learn to Talk to Your Objects
This article from the Vattenfall tech blog provides a concise summary of the principle's advantages.
Read the sections 'Benefits of Tell Don’t Ask principle', 'A little bit of theory', and 'Summary'. These sections reinforce the key advantages: localization of information, reduced duplication, and simplified testing.
Key Benefits:
- Improved Encapsulation: Data and the logic that manipulates it live together.
- Higher Cohesion: Classes are more focused on a single, clear purpose.
- Reduced Coupling: Client code is not dependent on the object's internal structure.
- Enhanced Maintainability: Business rules are in one place, making them easier to find and change.
However, this principle is a guideline, not an absolute law. There are situations where "asking" is perfectly acceptable.
"Tell, Don't Ask" Principle Explained in 100 Seconds
The short article '"Tell, Don't Ask" Principle Explained in 100 Seconds' gives clear guidance on when to use each approach.
Read the sections titled 'Use "Tell" When:' and 'Use "Ask" When:'. This will give you a balanced perspective on applying the principle.
It's okay to "Ask" when:
- Querying Data for Display: You need to retrieve data from an object simply to display it (e.g.,
user.getName()). This action doesn't involve any business logic or state change. - Using Data Transfer Objects (DTOs): DTOs are objects whose primary purpose is to carry data between processes or layers (e.g., from your service layer to a REST controller). They are designed to be simple data structures and are an exception to this principle.
- External Decisions: The decision logic depends on information from multiple, unrelated objects or external systems that the object itself shouldn't know about.
Test your understanding!
Imagine you are designing a BankAccount class. A business rule states that you cannot withdraw more money than the current balance.
Here is an implementation that follows the "Ask" style:
// "Ask" style client code
public class BankService {
public void processWithdrawal(BankAccount account, double amount) {
if (account.getBalance() >= amount) { // Asking for state
account.setBalance(account.getBalance() - amount); // Acting on that state
System.out.println("Withdrawal successful.");
} else {
System.out.println("Insufficient funds.");
}
}
}
How would you refactor the BankAccount class and the client code to follow the "Tell, Don't Ask" principle?
Show answer
To refactor, we move the withdrawal logic and the business rule check into the BankAccount class itself.
1. Create a withdraw method in BankAccount:
This method will contain the logic. It will either succeed and change the state, or it will fail (e.g., by returning a boolean or throwing an exception).
public class BankAccount {
private double balance;
public BankAccount(double initialBalance) {
this.balance = initialBalance;
}
// Public getter for display purposes is okay
public double getBalance() {
return balance;
}
// The "Tell" method
public void withdraw(double amount) {
if (amount > this.balance) {
throw new IllegalArgumentException("Insufficient funds for withdrawal.");
}
if (amount <= 0) {
throw new IllegalArgumentException("Withdrawal amount must be positive.");
}
this.balance -= amount;
}
// It's good practice to also have a deposit method
public void deposit(double amount) {
if (amount <= 0) {
throw new IllegalArgumentException("Deposit amount must be positive.");
}
this.balance += amount;
}
}
Note: We've removed the public setBalance method to protect the object's integrity. State changes can only happen through the withdraw and deposit methods.
2. Refactor the client code:
The BankService no longer contains any business logic. It simply tells the account to perform the withdrawal.
// "Tell" style client code
public class BankService {
public void processWithdrawal(BankAccount account, double amount) {
try {
account.withdraw(amount); // Telling the object what to do
System.out.println("Withdrawal successful. New balance: " + account.getBalance());
} catch (IllegalArgumentException e) {
System.out.println("Withdrawal failed: " + e.getMessage());
}
}
}
Now, the BankAccount is responsible for its own state and rules, making the system much more robust and maintainable.
Conclusion
The "Tell, Don't Ask" principle is a powerful mental model for writing true object-oriented code. It shifts the responsibility for business logic from external service classes onto the objects that actually own the data. This results in more cohesive, encapsulated, and autonomous objects that are easier to understand, maintain, and test.
Key Takeaways:
- Core Idea: Tell objects what to do; don't ask them for their state and then make decisions externally.
- Goal: Move behavior (logic) into objects to live alongside the data it operates on.
- Avoid Anemic Models: Strive to create rich domain objects with responsibilities, not just passive property bags with getters and setters.
- Result: Stronger encapsulation, higher cohesion, and lower coupling.
In our next lesson, we will discuss the importance of designing a clear and stable API. The principles we've covered—composition over inheritance, Law of Demeter, and Tell, Don't Ask—are all fundamental tools that help us achieve that goal. By creating well-defined, intention-revealing methods on our objects, we are inherently designing better APIs for them. We will now explore this concept more formally.