Course Content
Object-Oriented Design Interview
14 sections · 29 lessons
Composition, SOLID and UML class diagrams
The four pillars tell you how to build one class well. This lesson is about how classes fit together: holding an object instead of extending one, the five SOLID principles that describe a model which survives change, and the handful of UML notations you need to draw that model on a whiteboard.
Composition over inheritance
The rule of thumb that settles most modelling arguments in this round: prefer holding an object to extending a class. State it out loud when you use it — it is a sentence interviewers listen for.
The same design both ways
The problem: a coffee shop's drinks. A drink has a base price, and can have milk, an extra shot, or syrup, each adding cost.
With inheritance:
1class Coffee { Money cost() { return Money.rupees(100); } }2class CoffeeWithMilk extends Coffee { Money cost() { return super.cost().plus(rupees(20)); } }3class CoffeeWithMilkAndShot extends CoffeeWithMilk { /* +30 */ }4class CoffeeWithMilkAndShotAndSyrup extends CoffeeWithMilkAndShot { /* +25 */ }5class CoffeeWithShot extends Coffee { /* +30 */ }6class CoffeeWithSyrup extends Coffee { /* +25 */ }7class CoffeeWithShotAndSyrup extends CoffeeWithShot { /* +25 */ }8// ...Three optional add-ons give eight combinations. A fourth add-on gives sixteen. This is the class explosion, and the count doubles with every new option.
With composition:
1public class Drink {2 private final DrinkBase base;3 private final List<AddOn> addOns;45 public Money cost() {6 return addOns.stream().map(AddOn::price).reduce(base.price(), Money::plus);7 }8}Three add-ons is three small AddOn objects. A fourth add-on is a fourth object and zero new classes in the hierarchy. Better still, a customer can add syrup after ordering, because you can add to a list at runtime and you cannot change an object's class at runtime.
Why the inheritance version is worse, precisely
- Class count grows exponentially with independent options: 2ⁿ for n options.
- Runtime change is impossible. An object's class is fixed at construction.
- Combinations are hard-coded, so a combination nobody wrote does not exist.
- The base class is fragile (Inheritance, and its limits): changing
Coffee.costaffects all eight.
When inheritance still wins
Composition is not universally correct. Inheritance is better when the variants form a small closed set, differ in what they answer rather than what they are made of, and never need combining. Motorcycle, Car, and Truck each returning a different VehicleSize is the right use — there is no such thing as a MotorcycleTruck.
The test is whether the variations combine. If they do, composition. If they are mutually exclusive alternatives, inheritance is fine and shorter.
SOLID, explained through this round's problems
SOLID is five principles with an awkward acronym. Two of them are what interviewers actually probe in this round; the other three are worth one sentence each and a small example.
S — Single Responsibility Principle
A class should have one reason to change.
This is the one-sentence test from Phase 4: assigning responsibilities, restated. Violated:
1public class Order {2 void addItem(MenuItem item) { … }3 Money total() { … }4 String toReceiptHtml() { … } // changes when the receipt design changes5 void save(Connection db) { … } // changes when the database changes6 void emailConfirmation() { … } // changes when the mail provider changes7}Four unrelated reasons to change one file. A designer tweaking the receipt and a database migration now collide in the same class. Fixed: Order holds items and computes a total; a ReceiptRenderer, an OrderRepository, and a NotificationService each own one of the others.
O — Open/Closed Principle
Open for extension, closed for modification. You should be able to add behaviour by adding code, not by editing working code.
This is what the extension question at minute forty is testing, every time. Violated:
1Money fee(Ticket t) {2 if (t.size() == SMALL) return …;3 else if (t.size() == MEDIUM) return …;4 // new vehicle size ⇒ edit this method5}Fixed with a PricingStrategy per rule (Abstraction), or a rate table keyed by size. Either way, a new size adds an entry rather than editing a method.
L — Liskov Substitution Principle
A subclass must be usable anywhere its superclass is, without the caller knowing.
The standard violation: Square extends Rectangle. Rectangle lets you set width and height independently; Square cannot, so setWidth(5); setHeight(4); yields 4×4 instead of 5×4 and any caller written against Rectangle is now wrong. In this round it appears as a subclass that throws UnsupportedOperationException on an inherited method — a reliable sign the hierarchy is wrong.
I — Interface Segregation Principle
Many small interfaces beat one large one. A class should not be forced to implement methods it has no use for.
In the ATM design, one fat Hardware interface with dispense, printReceipt, readCard, and displayText forces the cash dispenser to implement three methods it will never use. Four small interfaces — CashDispenser, Printer, CardReader, Screen — let each device implement exactly what it does, and let a test stub replace one of them.
D — Dependency Inversion Principle
Depend on abstractions, not concrete classes. High-level policy should not import low-level detail.
OrderService should hold an OrderRepository interface, not a PostgresOrderRepository. That one change makes the service testable with an in-memory map and means swapping the database touches one line of wiring. In the interview it appears as "I'll assume a repository interface here" — the sentence from Phase 5: coding the core, and what to skip.
Reading and drawing UML class diagrams
You need five notations, not the whole Unified Modeling Language (UML) specification. Anything beyond these five costs time on a whiteboard and buys nothing.
The class box
Three compartments: name, fields, methods. Under time pressure, write the name and two or three fields that matter, and skip the methods except where they carry the design.
┌─────────────────────┐│ ParkingSpot │├─────────────────────┤│ - id: String │ - private│ - size: SpotSize │ + public│ - occupied: boolean │├─────────────────────┤│ + assign(v: Vehicle)││ + release() │└─────────────────────┘The four relationships you need
| Relationship | Line | Means | Example |
|---|---|---|---|
| Association | plain line, arrow for direction | "knows about" | Ticket → Vehicle |
| Aggregation | hollow diamond at the owner | "has, but parts survive alone" | Team ◇— Player |
| Composition | filled diamond at the owner | "owns; parts die with it" | Floor ◆— ParkingSpot |
| Inheritance | hollow triangle at the parent | "is a" | Car —▷ Vehicle |
The aggregation/composition distinction is the one interviewers ask about. The test: if the container is destroyed, does the part still make sense? Destroy a floor and its parking spots are meaningless — composition, filled diamond. Disband a team and the players still exist — aggregation, hollow diamond. If you are unsure in the room, use a plain association line and say what the lifecycle is in words. Nobody has failed for using an association line; people do lose time drawing diamonds they cannot justify.
Multiplicities
Numbers at each end of the line, and they carry real information:
ParkingLot 1 ◆——— 1..* Floor 1 ◆——— 1..* ParkingSpot 0..1 ——— 0..1 VehicleRead: one lot has one or more floors; each floor has one or more spots; a spot holds zero or one vehicle and a vehicle occupies zero or one spot. Common values are 1, 0..1, 1..*, 0..*, and exact numbers like 2 for a blackjack starting hand.
The 0..1 on both ends of the spot-to-vehicle line is where a design conversation starts: it is exactly the pair that concurrency can violate by putting two vehicles in one spot.
Drawing it legibly under time pressure
Five habits that make a diagram readable at minute twenty-five:
- Lay the boxes out before drawing lines. Central entity in the middle, its parts around it, hardware or external services at the edge.
- Leave a fist of space between boxes. Lines need somewhere to go, and every candidate underestimates this.
- Lines horizontal and vertical only. Diagonals cross and become unreadable.
- Write multiplicities last, in one pass at the end.
- Say what you are drawing while you draw it. Fifteen silent seconds is a long time in an interview.