lesson 2/5 · 0:55
Open/Closed Principle
Add a new payment method without editing Checkout, by replacing if-else chains with one class per method.
“Software should be open for extension, but closed for modification.”
The story
Tony’s menu is painted on the wall, so every new pizza means repainting the whole wall.
The problem in code
Code has painted walls too: one Checkout with an if-else chain for charging, another for refunds, another for fees. Adding Apple Pay means editing every chain. Miss one, and Apple Pay refunds fail.
class Checkout {
pay(method: string, amount: number) {
if (method === 'card') { /* ... */ }
else if (method === 'paypal') { /* ... */ }
else if (method === 'applepay') { /* ... */ }
}
refund(method: string, amount: number) {
if (method === 'card') { /* ... */ }
else if (method === 'paypal') { /* ... */ }
// forgot applepay: refunds silently fail
}
}
The fix
Each payment method becomes its own class with pay, refund and fee. Checkout just calls them, so adding a method never opens Checkout again.
interface PaymentMethod {
pay(amount: number): Promise<void>;
refund(amount: number): Promise<void>;
fee(amount: number): number;
}
class Checkout {
constructor(private method: PaymentMethod) {}
async charge(amount: number) {
await this.method.pay(amount + this.method.fee(amount));
}
}
// Adding crypto is one new class. Checkout is untouched.
class CryptoPayment implements PaymentMethod {
async pay(amount: number) { /* ... */ }
async refund(amount: number) { /* ... */ }
fee(amount: number) { return amount * 0.01; }
}
Takeaway
When the same if (type === ...) shows up in several places, each new type means editing all of them. Turn the types into classes behind one interface.