Object-Oriented Design Interview

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:

Java
class Coffee { Money cost() { return Money.rupees(100); } }class CoffeeWithMilk extends Coffee { Money cost() { return super.cost().plus(rupees(20)); } }class CoffeeWithMilkAndShot extends CoffeeWithMilk { /* +30 */ }class CoffeeWithMilkAndShotAndSyrup extends CoffeeWithMilkAndShot { /* +25 */ }class CoffeeWithShot extends Coffee { /* +30 */ }class CoffeeWithSyrup extends Coffee { /* +25 */ }class CoffeeWithShotAndSyrup extends CoffeeWithShot { /* +25 */ }// ...

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:

Java
public class Drink {    private final DrinkBase base;    private final List<AddOn> addOns;    public Money cost() {        return addOns.stream().map(AddOn::price).reduce(base.price(), Money::plus);    }}

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.

Inheritance — a rigid treeEmployee+ pay()+ report()SalariedEmployee+ pay()HourlyEmployee+ pay()«does not fit»SalariedContractor?+ pay() ← which one?A new axis — contractor or employee — forces either a second tree or acombinatorial explosion of subclasses.Composition — behaviour plugged inWorker- payPolicy: PayPolicy+ pay()«interface»PayPolicy+ amountFor(period)delegates to1SalariedPay+ amountFor(period)HourlyPay+ amountFor(period)A contractor is a Worker with a different PayPolicy. No new subclass,and the policy can change at runtime.notationinheritance — is aaggregation — has, but can outliveimplements an interface
Inheritance fixes the axis of variation at compile time; composition leaves it open, which is why "favour composition" is the default advice.

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.cost affects 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.

Five principles, two of them heavily probedSOLID hereS: one axis of changeO: extend, do not editL: subtypes substituteI: small interfacesD: invert dependencies
Single Responsibility and Open/Closed are what get probed; the other three earn one sentence each.

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:

Java
public class Order {    void addItem(MenuItem item) { … }    Money total() { … }    String toReceiptHtml() { … }        // changes when the receipt design changes    void save(Connection db) { … }      // changes when the database changes    void emailConfirmation() { … }      // changes when the mail provider changes}

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:

Java
Money fee(Ticket t) {    if (t.size() == SMALL) return …;    else if (t.size() == MEDIUM) return …;    // new vehicle size ⇒ edit this method}

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.

Text
┌─────────────────────┐│  ParkingSpot        │├─────────────────────┤│ - id: String        │      -  private│ - size: SpotSize    │      +  public│ - occupied: boolean │├─────────────────────┤│ + assign(v: Vehicle)││ + release()         │└─────────────────────┘

The four relationships you need

RelationshipLineMeansExample
Associationplain line, arrow for direction"knows about"Ticket → Vehicle
Aggregationhollow diamond at the owner"has, but parts survive alone"Team ◇— Player
Compositionfilled diamond at the owner"owns; parts die with it"Floor ◆— ParkingSpot
Inheritancehollow 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:

Text
ParkingLot 1 ◆——— 1..* Floor 1 ◆——— 1..* ParkingSpot 0..1 ——— 0..1 Vehicle

Read: 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.

UML notation you actually need in an interviewAccount- balance: Money# owner: Customer+ id: String+ deposit(Money)- audit()+ balanceOf() Moneyclass nameattributes — stateoperations — behaviourvisibility+public — part of the contract#protected — subclasses only-private — implementation detailsay the visibility out loud; interviewers listen for itnotationassociation — knows aboutaggregation — has, but can outlivecomposition — owns; dies with itinheritance — is aimplements an interfacedepends on — uses transientlymultiplicity1exactly one0..1optional1..*one or more*any number, including none2..4a bounded rangeaggregation or composition?Ask one question: if the whole is deleted, does thepart still make sense on its own?Yes → aggregation (a Team and its Players). No →composition (an Order and its OrderLines).
Six relation types, four visibility marks and multiplicity cover essentially every diagram this round asks for.

Drawing it legibly under time pressure

Five habits that make a diagram readable at minute twenty-five:

  1. Lay the boxes out before drawing lines. Central entity in the middle, its parts around it, hardware or external services at the edge.
  2. Leave a fist of space between boxes. Lines need somewhere to go, and every candidate underestimates this.
  3. Lines horizontal and vertical only. Diagonals cross and become unreadable.
  4. Write multiplicities last, in one pass at the end.
  5. Say what you are drawing while you draw it. Fifteen silent seconds is a long time in an interview.