on
Tell Don’t Ask Principle
Whenever we create a new application, designing classes is among the first and important tasks because the taken decisions at that phase will and could affect the rest of our work. So to succeed this step, we should apply the best practices and principles. Especially when defining the boundaries and responsibilities of each class as we are doing object-oriented programming (OOP) which is unlike the procedural programming (PP).
Procedural code gets information then makes decisions. Object-oriented code tells objects to do things. — Alec Sharp
I will not talk about PP but I just want to clarify that OOP and PP are two different programming paradigms. For some applications or domains, OOP is suitable and it is not better than PP, and vice versa. In the past when I started learning computer and programming and algorithm I used PP languages like Pascal and C, then I learned Java and OOP. To be honest and as I remember this change was not easy. OOP first requires a mind-shift, and here it comes Tell Don’t Ask principle. This principle focuses on the design of classes particularly their behavior, and by hiding their internal working using encapsulation techniques.
To understand the tell and the ask, let’s write a code to show them separately. These examples are about a loyalty card system that removes and adds points to a loyalty card class.
1. Ask version
Let’s define the class LoyaltyCard
:
class LoyaltyCard {
private int id;
private int balance;
// getter, constructor removed for brevity's sake
}
To manage the points, we wrote a class to add and remove points from a LoyaltyCard
object:
class LoyaltyCardService {
// constructor removed for brevity's sake
void addPoints(int id, int points) {
LoyaltyCard card = //... find card by id in db
card.balance += points;
// persist the LoyaltyCard
}
void removePoints(int id, int points) {
LoyaltyCard card = //... find card by id in db
if (card.balance < points)
throw new Exception("Not enough points.");
card.balance -= points;
// persist the LoyaltyCard
}
}
Can you spot bad design when you see it? Can you tell what’s wrong down ?
In this version, LoyaltyCard
is a data holder and does not have any logic. Moreover, LoyaltyCardService
does all the work and it always asks the LoyaltyCard
the balance to add or remove points. LoyaltyCardService
has a lot of responsibilities that shouldn’t have.
So what will be the design if the business asked to have Standard
, Premium
, and Gold
Loyalty Cards? For example, if the card is Premium
, we need to add an extra point to the balance and two extra points if it is Gold: So we’ll have to modify the LoyaltyCard
and LoyaltyCardService
classes and we will end up with messy code and logic of LoyaltyCard
inside LoyaltyCardService
. At a moment, we will not know who’s responsible for what.
The answer is to make LoyaltyCardService
do simple operations add/remove points and nothing else.
2. Tell version
Let’s add the card type in the LoyaltyCard
class:
class LoyaltyCard {
private int id;
private int balance;
private int cardType;
// getter, constructor removed for brevity's sake
void addPoints(int points) {
int extraPoints = 0;
if (cardType == 1) extraPoints = 1;
if (cardType == 2) extraPoints = 2;
balance = balance + points + extraPoints;
}
void removePoints(int id, int points) {
if (card.balance < points)
throw new Exception("Not enough points.");
card.balance -= points;
}
}
In this version, the LoyaltyCardService
focuses on its responsibility and it has no logic.
class LoyaltyCardService {
// constructor removed for brevity's sake
void addPoints(int id, int points) {
LoyaltyCard card = //... find card by id in db
card.addPoints(points);
// persist the LoyaltyCard
}
void removePoints(int id, int points) {
LoyaltyCard card = //... find card by id in db
card.removePoints(points)
// persist the LoyaltyCard
}
}
All the logic of adding or removing points, extra points, and card type is now in the LoyaltyCard
class, where it should be. Moreover, to protect the balance inside the LoyaltyCard
class against unintentional uses, only the LoyaltyCard
instance can update its balance.
Now, the logic can be extended in LoyaltyCard
without changes in LoyaltyCardService
.
3. Conclusion
In this tutorial, we have seen the Tell, Don’t Ask Principle and seen bad and good examples. The Tell, Don’t Ask principle lets us concentrate on your class responsibilities and the functionalities we want them tp provide. Note, to act, we don’t have to ask the objects about their state, only instruct them to.