Polymorphism
Month end. Every account gets charged its fee, and the bank now sells three products. Here is the loop most people write first:
It works. It is also the thing you will be editing forever: every new product means finding this chain — and the four others like it scattered through the codebase — and adding a branch. Miss one and the product silently bills nothing.
Move the answer into the type
Polymorphism is the fix: stop asking what something is, and let it answer for itself.
The loop has no branches left. Adding a fourth product is a new class and nothing else — the billing code never learns it exists.
One line of source, many pieces of code
a.monthly_fee() is a single line, but it is not a single function. Python looks
the method up on the object in front of it, every time it runs:
Watch after_fee. It is written once, on the parent, and it existed before
Savings did — yet self.monthly_fee() finds the child's version. The parent
calls down into code it has never heard of.
class Fee:
def amount(self):
return 5.0
def receipt(self):
return f"charged {self.amount()}"
class Free(Fee):
def amount(self):
return 0.0
print(Free().receipt())No base class required
Notice what the billing loop never did: check a type, or demand a shared parent.
Current, Savings and Student above inherit from nothing at all.
class Wallet:
def monthly_fee(self):
return 2.0
def total(items):
return sum(i.monthly_fee() for i in items)
class Voucher:
def monthly_fee(self):
return -1.0
print(total([Wallet(), Voucher()]))Inheritance is one way to guarantee the method exists. It is not the only one, and Python does not insist on it.
Type checks are the smell
Once behaviour lives on the type, an isinstance or type(...) test inside a
loop is usually a bug waiting for its second subclass:
A student is being billed nothing. Make it print 6.0.
The repaired loop is also the shorter one. That is the usual shape of this refactor: the branches don't move somewhere else, they stop existing.
You have been using it all along
len() has no idea what a string is. It asks.
Three unrelated types, one call, three different pieces of code underneath. Lesson 4 puts your own types on the receiving end of that.
🎯 Your turn
Define Current, Savings and Student, each with monthly_fee() returning
5.0, 0.0 and 1.0. Write monthly_run(kinds) which builds the matching
account for each name and returns the total fee, rounded to two decimals:
monthly_run(["current", "savings"])→5.0monthly_run(["current", "student", "student"])→7.0monthly_run([])→0
Hint — map the names to classes with a dict, then sum. The summing loop
should contain no if at all.
Then press ▶ Run and hit ✓ Check. ✅
