The “Tell, don’t ask” principle

This is one of my favorite principles because it is easy to spot when violated and because it helps in having proper API surfaces when applied.

Tell, don’t ask

In essence this principle proposes that instead of asking from an instance for its values in order to decide how the same instance will execute something, just tell the instance to execute it. It knows its own state, it can make its own decisions!

Asking

By asking we actually refer to accessing many of a class’s properties. For example:

// somewhere in the code
if (task.status == Status.NotStarted && task.createdAt < LocalDate.of(2021, 1, 14) && task.subscribers.isEmpty()) {
task.close()
}
// which might have led to this API
class Task(
initialStatus: Status,
val createdAt: LocalDate
) {
var status: Status = initialStatus
private set
private val innerSubscribers = mutableListOf<Subscriber>()
val subscribers: List<Subscriber> = innerSubscribers
fun add(subscriber: Subscriber) {
innerSubscribers.add(subscriber)
}
fun close() {
status = Status.Resolved
}
}

in the code above we ask the task to provide the values of three of its properties in order to decide if we are going to close it or not.
In the process we (a) might had to expose those properties just for this code (creation date and subscribers could be private) and (b) inevitably leaked business logic that involves a task (when a task is eligible for closing).

Telling

Lets change the code in order to tell the task to close itself if possible:

// somewhere in the code
task.closeIfUnclaimed(createdBefore = LocalDate.of(2021, 1, 14))
// task's better API
class Task(
initialStatus: Status,
private val createdAt: LocalDate
) {
var status: Status = initialStatus
private set
private val subscribers = mutableListOf<Subscriber>()
fun add(subscriber: Subscriber) {
subscribers.add(subscriber)
}
fun closeIfUnclaimed(createdBefore: LocalDate): Boolean {
if (status == Status.NotStarted && createdAt < createdBefore && subscribers.isEmpty()) {
status = Resolved
return true
}
return false
}
}

There are two things that we gain from this change:

  1. we moved the “closing” logic in the Task itself which allows us to contain future logic alterations in just one place.
  2. the class’s API exposes only the necessary helping us to avoid accidental couplings.

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out /  Change )

Google photo

You are commenting using your Google account. Log Out /  Change )

Twitter picture

You are commenting using your Twitter account. Log Out /  Change )

Facebook photo

You are commenting using your Facebook account. Log Out /  Change )

Connecting to %s