Stop Asking for Data! Master the 'Tell, Don't Ask' Principle

Published: (December 18, 2025 at 07:57 PM EST)
2 min read
Source: Dev.to

Source: Dev.to

Introduction

One of the most common mistakes in Object‑Oriented Programming (OOP) is treating objects like simple data structures. We often pull data out of an object, perform some logic, and then put the result back in. This article explains the Tell, Don’t Ask (TDA) principle and shows how it improves your code using a simple Shop and Account example.

The Tell, Don’t Ask Principle

  • Don’t Ask: Do not ask an object for its data to perform logic yourself.
  • Tell: Tell the object what to do, and let it handle its own state.

Following TDA improves encapsulation and cohesion.

Anti‑Pattern Example

The Shop class manages logic that belongs to the Account, violating encapsulation.

package com.mateandgit.programming.chapter1_2.problem;

public class Shop {

    public void sell(Account account, Product product) {
        // ❌ BAD: Asking for data (getMoney)
        long price = product.getPrice();
        long mileage = account.getMoney();

        // ❌ BAD: The Shop decides if the account has enough money
        if (mileage >= price) {
            // ❌ BAD: Modifying the account's state externally
            account.setMoney(mileage - price);
            System.out.println(product.getName() + " purchased.");
        } else {
            System.out.println("Insufficient balance.");
        }
    }
}

Problems

  • Violation of EncapsulationShop knows the internal details of Account (its money field and how the balance is calculated).
  • Logic Duplication – Any other class that needs to deduct money must duplicate the if (balance >= price) check.
  • Hard to Maintain – Changing validation rules (e.g., “minimum balance must remain 100”) requires updates in every class that accesses Account.

Refactored Solution

Move the logic into the Account class. The Shop now only commands the Account.

package com.mateandgit.programming.chapter1_2.solution;

public class Shop {

    public void sell(Account account, Product product) {
        // ✅ GOOD: Ask a question (canAfford)
        if (account.canAfford(product.getPrice())) {

            // ✅ GOOD: Tell the object to do something (withdraw)
            account.withdraw(product.getPrice());
            System.out.println(product.getName() + " purchased.");
        } else {
            System.out.println("Insufficient balance.");
        }
    }
}

Inside the Account class you would have methods like canAfford() and withdraw() that encapsulate the logic.

Benefits of the Refactor

  • Better EncapsulationShop no longer knows how the withdrawal happens; it just sends a message.
  • Reusable Logic – Validation rules (canAfford) are centralized in Account.
  • Readability – The code reads like a sentence: “If account can afford, withdraw.”
  • Higher Cohesion & Lower Coupling – Objects manage their own state, leading to cleaner, more maintainable code.

Applying Tell, Don’t Ask

  • Don’t: getSomething() → calculate → setSomething().
  • Do: Create a method in the object and tell it to perform the action.

Result: high cohesion, low coupling, and better encapsulation.

Back to Blog

Related posts

Read more »

Method overriding in Java

What is method overriding When a subclass provides a specific implementation for a method that is already defined in its parent class, it is called method over...