Template method
Part of the free Low-Level Design course on LogicWiz, in the chapter: Behavioural patterns II. The class-diagram exercises run in the app.
The tax fix that reached only one payout
"Finance changed the TDS rounding. Someone fixed the rider payout. The store payout still uses the old rule, and the tax filing does not match the books."
Priya, reading the incident report
Two classes, one copied process
Every Monday, SwiftKart pays two groups. Riders are paid 30 rupees per delivery. Dark stores, the small warehouses that pack orders, get 8% of their sales. Both payouts follow the same five steps:
- Load last week's entries.
- Work out the gross amount.
- Deduct TDS. TDS, tax deducted at source, is the 1% of the payment that SwiftKart sends to the tax office.
- Transfer the rest to the bank account.
- Send a statement.
Arjun wrote two classes. Here are their run methods side by side:
public class RiderPayout {
// fields: deliveryLog, bank, mailer, riderAccount
public void run(LocalDate week) {
List<Entry> entries = deliveryLog.forWeek(week);
int gross = entries.size() * 30;
int tds = (int) Math.round(gross / 100.0);
bank.transfer(riderAccount, gross - tds);
mailer.sendStatement(riderAccount, week, gross - tds);
}
}
public class StorePayout {
// fields: salesLog, bank, mailer, storeAccount
public void run(LocalDate week) {
List<Entry> entries = salesLog.forWeek(week);
int gross = totalSales(entries) * 8 / 100;
int tds = gross / 100; // old rule: cuts off instead of rounding
bank.transfer(storeAccount, gross - tds);
mailer.sendStatement(storeAccount, week, gross - tds);
}
}
Only the first two lines of each method differ. The last three are copies. When finance changed the rounding rule, the fix went into one copy and not the other.
The problems go beyond this one bug:
- Every rule change is two edits. A third payout, say for packers, would make it three.
- Nothing fixes the order of steps. A new developer could transfer the money before deducting tax, and the code would still compile.
- Each class tests the whole process. You cannot test "the rider amount is 30 per delivery" without a bank and a mailer.
The idea in plain words
Think of a restaurant kitchen that makes three kinds of dal. The head chef writes one recipe card for all of them:
- Soak the lentils.
- Boil them.
- Add the tadka, the spices fried in oil or ghee.
- Garnish if you like.
- Serve hot.
The steps and their order never change. Each dal fills in the details. Toor dal uses mustard seeds and curry leaves in its tadka. Dal makhani uses butter and cream. "Garnish if you like" is optional: one dal adds coriander, another skips it.
The Template Method pattern does this in code. A base class has one method, the template method, that runs the steps in a fixed order. Some steps are written once in the base class. Others are left for each subclass to fill in. The subclasses cannot change the order. They only supply the details.
Where the analogy stops working: a cook reading a card can still skip a step or swap two. In code, the base class runs the steps itself, and the subclass cannot change the order at all. Also, a recipe card is advice, while a template method is enforced by the compiler.
The structure
The roles are few:
- Abstract class: a class you cannot create with
new. It may leave some methods without a body for subclasses to write. Here it isPayoutJob. - Template method: a
finalmethod in the abstract class that calls the steps in order.finalmeans no subclass can override it. Here it isrun. - Steps: abstract steps that every subclass must write, and fixed steps that no subclass can change.
- Hooks: steps with a default body that a subclass may override, such as the optional garnish.
- Concrete classes:
RiderPayoutandStorePayout, which fill in the abstract steps.
classDiagram
class PayoutJob {
<<abstract>>
+run(week) void
#load(week)* List
#calculate(entries)* int
#shouldPay(net) boolean
-tds(gross) int
}
PayoutJob <|-- RiderPayout
PayoutJob <|-- StorePayout
The base class calls the subclass's steps, never the other way round. This is sometimes called the Hollywood principle: "Don't call us, we'll call you."
Build it step by step
Step 1: the abstract base with a final run.
public abstract class PayoutJob {
private final Bank bank;
private final Mailer mailer;
protected PayoutJob(Bank bank, Mailer mailer) {
this.bank = bank;
this.mailer = mailer;
}
public final void run(LocalDate week) {
int gross = calculate(load(week));
int net = gross - tds(gross);
if (shouldPay(net)) {
bank.transfer(account(), net);
mailer.sendStatement(account(), week, net);
}
}
protected abstract List<Entry> load(LocalDate week);
protected abstract int calculate(List<Entry> entries);
protected abstract String account();
protected boolean shouldPay(int net) { return net > 0; } // hook
private int tds(int gross) { return (int) Math.round(gross / 100.0); }
}
The steps are protected: subclasses can see and override them, but other classes cannot call them. Outside code sees only run. Now read the methods by kind. run is the template method, and final keeps the order safe. load, calculate and account are abstract: every payout must write them. tds is private, so the tax rule lives in one place and no subclass can change it. shouldPay is a hook with a sensible default.
Step 2: a subclass fills in only what differs.
public class RiderPayout extends PayoutJob {
private final DeliveryLog log;
private final String riderAccount;
public RiderPayout(Bank bank, Mailer mailer, DeliveryLog log, String riderAccount) {
super(bank, mailer);
this.log = log;
this.riderAccount = riderAccount;
}
protected List<Entry> load(LocalDate week) { return log.forWeek(week); }
protected int calculate(List<Entry> entries) { return entries.size() * 30; }
protected String account() { return riderAccount; }
}
RiderPayout has no tax code and no bank code. Its test checks one thing: 10 deliveries give 300 rupees.
Step 3: a hook for the one place a subclass is different. Stores are paid only when the amount reaches 500 rupees. Smaller amounts wait for next week.
public class StorePayout extends PayoutJob {
// constructor, load, calculate and account() as in RiderPayout,
// but reading from the sales log and taking 8% of sales
@Override
protected boolean shouldPay(int net) { return net >= 500; }
}
The caller does not care which payout it has: new RiderPayout(bank, mailer, log, account).run(week). A packer payout next quarter is one more subclass, and it gets the correct TDS rule for free.
When to use it, and the risk that comes with it
Use template method when several flows share the same steps in the same order, only some steps differ, and the order must be enforced, as with tax before transfer. Do not use it to share code between classes that are not really the same kind of thing.
It is built on inheritance, so it brings inheritance's costs. Java allows one parent class, so RiderPayout cannot extend anything else. A subclass is also tied to the details of its base class. This is the fragile base class problem: a change in a base class breaks subclasses that relied on how it used to work.
Here is how that happens. Say someone edits run to call shouldPay(gross) before the tax is taken off. StorePayout wrote its 500 rupee rule for the amount after tax. A store that earned 503 rupees before tax and 498 after is now paid, against the rule. Nothing in StorePayout changed, and it broke. Keep templates short, keep fixed steps private, and write down what each hook receives, such as "the amount after tax".
💡 Tip: Make the template method
finaland the fixed stepsprivate. Then the only things a subclass can change are the steps you chose to open.
Template method or strategy? In a mock interview, a candidate said: "Template method and strategy are the same. Both let you change one step." Both do let you vary behaviour, but in different ways:
- Template method uses inheritance. The varying step lives in a subclass. It is fixed when the object is created.
- Strategy uses composition. The varying step is an object held in a field. It can be swapped at runtime, and each step can have its own strategy.
Watch what happens when a second thing varies. Say statements can go by email or WhatsApp. With inheritance you need RiderEmailPayout, RiderWhatsAppPayout, StoreEmailPayout and StoreWhatsAppPayout. Every new channel adds one class per payout type. At that point, keep the template for the fixed steps and pass a StatementSender strategy into it. Many teams start with composition for this reason.
In interviews, template method shows up wherever a process has fixed steps. In a logging framework, an abstract appender checks the level and formats the line, then calls an abstract write. Board games such as snake and ladder have a fixed loop: set up, take turns until someone wins, announce the winner. Report exports (load, format as CSV or PDF, save) and data import jobs follow the same shape.
Java itself uses the pattern. To write your own list, you extend AbstractList and write only get and size. The base class builds contains, indexOf and looping on top of those two steps.
Practice: write the payout steps once
The starter has two payout classes that copy the same five steps. Add an abstract payout class with a template method that fixes the steps and declares the ones that vary, and make both payouts extend it.
📌 Summary: Template method fixes the order of steps in a final base class method; subclasses fill in the steps that vary, and hooks are optional. It uses inheritance; strategy uses composition and can change at runtime.
What Arjun learns next
Arjun now has many small classes that call each other: the checkout runs a chain, the chain calls the stock service, the payout job calls the bank. Priya asks him to show, in order, who calls whom when a customer taps Pay. That is a sequence diagram.
Check your understanding
- Why is PayoutJob.run() marked final?
- Stores must be paid only when the net amount is at least 500 rupees. Riders have no minimum. Where does this rule go?
- A candidate says: "Template method and strategy are the same, both change one step." What is the main difference?
- Payouts now vary in two ways: how the amount is worked out, and whether the statement goes by email or WhatsApp. What is the best design?