Encapsulation and information hiding
Chapter 2 introduced visibility modifiers.
This chapter looks at the thinking behind them: why you should use private in the first place.
The danger of public properties
Look at the Stock class below.
It represents a stock quantity.
class Stock
{
public int $quantity = 0;
}
Because $quantity is public, its value can be rewritten freely from outside the class.
$stock = new Stock();
$stock->quantity = -5;
A negative stock quantity is impossible as a business state, but PHP's syntax cannot prevent it.
No matter where the Stock class is used, anyone can make this invalid assignment.
tell, don't ask
Simply turning a public property into a private one does not solve this. Adding a getter and a setter as below leaves the value just as freely rewritable from outside.
class Stock
{
private int $quantity = 0;
public function getQuantity(): int
{
return $this->quantity;
}
public function setQuantity(int $quantity): void
{
$this->quantity = $quantity;
}
}
// Caller
if ($stock->getQuantity() >= 5) {
$stock->setQuantity($stock->getQuantity() - 5);
}
In this code the caller is the one deciding "can we take 5 off the stock?" Every additional place that reduces stock has to duplicate this condition check. Forget the check in one place, and the stock easily goes negative.
The same thing can be rewritten like this.
class Stock
{
private int $quantity = 0;
public function reduce(int $amount): void
{
if ($this->quantity < $amount) {
throw new RuntimeException('Not enough stock');
}
$this->quantity -= $amount;
}
}
// Caller
$stock->reduce(5);
The caller just says "please take 5 off."
Deciding whether there is enough stock has moved inside a method on the Stock class itself.
Rather than asking for internal state and deciding at the call site (ask), you tell the object what you want done (tell). This idea is called tell, don't ask1. Putting the decision inside the object collects it in one place, so it stops being duplicated.
Deciding whether you need getters and setters
tell, don't ask does not mean you should get rid of every getter and setter. Fowler himself has voiced concern about the principle being taken too far, into a fashion for eliminating every query method2.
The rule of thumb is what the caller does after the method returns.
- If the caller uses the return value to decide or calculate something, consider whether that decision or calculation could move into a method on the object itself. The
reducemethod above is exactly that - If the return value is only displayed or printed as is, a getter is enough. Logging a total does not need dedicated decision logic
Summary
- A public property can be rewritten freely from outside, so it cannot prevent invalid states
- Adding a getter and a setter leaves the decision sitting with the caller
- The tell, don't ask principle is about handing the decision to the object (tell) rather than asking for internal state and deciding outside (ask)
- It does not mean getters and setters are unnecessary; the dividing line is whether an operation carries a decision or calculation that belongs in the tell style
What to read next
The next chapter covers the single responsibility principle. We look at the signs of a god class and work through an example of splitting responsibilities.
Exercises
Rewrite the following BankAccount class to follow the tell-don't-ask principle
class BankAccount
{
private int $balance = 0;
public function getBalance(): int
{
return $this->balance;
}
public function setBalance(int $balance): void
{
$this->balance = $balance;
}
}
// Caller: making a withdrawal
if ($account->getBalance() >= $amount) {
$account->setBalance($account->getBalance() - $amount);
} else {
throw new RuntimeException('Insufficient balance');
}
Sample answer
class BankAccount
{
private int $balance = 0;
public function withdraw(int $amount): void
{
if ($this->balance < $amount) {
throw new RuntimeException('Insufficient balance');
}
$this->balance -= $amount;
}
}
// Caller
$account->withdraw($amount);
Deciding whether the balance is sufficient has moved inside the withdraw method.
However many places make withdrawals, none of them has to duplicate that decision.