Production LLD
Design smells
Friday 6:12 PM. You open ParkingLotManager to add electric vehicles. The class is 800 lines, fee is a double, Ticket asks lot.getFloor().getSpot().getRate(), and the new vehicle type will touch seven files.
A design smell is a recurring structure that makes change risky, often before you know which pattern to apply. Those four sights have names: , , , and . Naming them makes the next refactor obvious.
What you are looking at
XYZManager with every verb. Split by reason to change; want a thin controller plus strategies (swappable rules).
String email, long startMs, and a raw fee. Replace with Money, Email, Instant value objects (types that guard their own rules).
A ParkingLot method that only talks to Ticket fields. Move it, or give the expert a richer method.
What actually happens when a smell sits in the lot
The Friday edit hurts because those structures hide more than one reason to change. A parks, prices, persists, and notifies, so a pricing tweak can break SMS copy. Line count alone is not the smell; mixed reasons are.
leaves fee, email, and startMs as raw types, so you cannot forbid negative money on a double. is a method more interested in another object's data than its own; the fix is to move it, or give the expert a richer method. Law of Demeter (talk only to immediate neighbors) is the same family: a getter chain reaches past neighbors, so you ask one neighbor instead of walking the graph.
You do not need to recite Fowler's full catalog. See ParkingLotManager and name the structure, then refactor. The catalog below is only for the smells that are not already on the Friday board.
How to choose the next refactor
Once you can name the Friday smells, the rest of the catalog is the same habit: pick the matching row, then take the smallest move.
Catalog
Leave a Strategy, Factory, or registry seam so the next type is one class.
Split the class by reason to change (Single Responsibility, or SRP).
Hide HashMap or JPA (persistence types) behind a port so callers never see the structure.
Bundle start, end, or plate fields into TimeRange, Plate, or Address.
Delete the unused interface; You Aren't Gonna Need It (YAGNI).
Simple rule of thumb
Name the smell, then choose the smallest move that removes it. A Strategy (an interchangeable algorithm) or Factory (a type that constructs the right class) comes after the name, not before.
From a messy fragment to a type that cannot go negative
The Friday class, slowed down. Each comment is a smell you can point at before you reach for a pattern name.
class ParkingLotManager { // God object
double fee; // primitive obsession (Money)
String email; // primitive obsession (Email)
Ticket park(String type, String plate, double hours) { /* ... */ }
// callers: ticket.getSpot().getFloor().getLot().getRate() // feature envy / Demeter
Instant now = LocalDateTime.now().toInstant(ZoneOffset.UTC); // hidden clock
}A often carries doubles because "it is just a fee." The value object is the smallest refactor that removes a whole class of bugs. shrinks too: callers stop formatting "rupees" in three places.
Money kills primitive obsession in one constructor
Construction is the gate. Once Money exists, negative amounts fail here instead of in a formatter three screens away (see in the preview).
final class Money {
private final long paise;
static Money rupees(long rupees) {
if (rupees < 0) throw new IllegalArgumentException("negative money");
return new Money(rupees * 100);
}
Money plus(Money other) { return new Money(paise + other.paise); }
Money times(long n) { return new Money(paise * n); }
}You see, you say, you do
The same fragment, compressed. Use this as a board script, not as a second copy of the definitions above.
| You see | You say | You do |
|---|---|---|
| ParkingLotManager 800 lines | God object | Split verbs; keep a thin controller |
| double fee | Primitive obsession | Money with a constructor invariant |
| lot uses only Ticket getters | Feature envy | Move method / PricingStrategy |
| EV add edits 7 files | Shotgun surgery | Seam on allocation and pricing |
| Cache.getMap().put | Leaky abstraction | put/get on a Cache port |
| IVehicle with one impl | Speculative generality | Delete until a second impl exists |
How to explain this in an interview
Interviewers do not need Fowler's full catalog. They need you to point at a board and name , , , or without reaching for a pattern first. Try this script on ParkingLotManager:
- 1"This class parks, prices, saves, and texts: God object. I would keep a thin ParkingService."
- 2"Fee is a primitive: Money belongs in its own type, not a raw field."
- 3"Exit only talks to Ticket getters: feature envy. Move the formula to PricingStrategy."
- 4"A new EV type would edit this class and the printer: shotgun surgery, the seam."
- 5"LocalDateTime.now() here is static global time. Clock at the edge."
Related smells you will also see
The script covers the Friday board. These six show up on the same designs and use the same "name it, then move" habit.
Refused bequest
A subclass that no-ops half the parent is using the wrong inheritance.
Long parameter list
Same family as primitive obsession: wrap args in a Command or Parameter Object.
Static global state
A public static clock that nobody can fake in a test.
Message chains
The getter chain ticket.getSpot().getFloor().getLot().getRate() skips the expert and walks the graph.
Lazy class
A type that does nothing after a half-finished extract. Delete or merge.
Temporary field
Set only for one algorithm. That algorithm wants its own object.
Common mistakes
Calling every large class a God object. A facade (a thin front that delegates) can look wide and still be thin. The smell is mixed reasons to change, not line count alone.
Fixing smells by adding patterns blindly. : Strategy on a class that will never vary. Name the smell, then choose the smallest move that removes it.
Ignoring primitive obsession because "it compiles." double amount will go negative. String email will be blank. Value objects exist to fail construction early.
Wrap-up
You have a Friday fragment, a catalog, and a board script. Check that the names stick, then follow the related notes for the refactors those names point to.
Check yourself
- Point at ParkingLotManager and name three smells in one fragment.
- Is a long facade automatically a (see the table)? Why or why not?
- versus Law of Demeter (talk only to neighbors): how do the fixes differ?
Key takeaway
See , , and before you reach for a pattern name. Naming the smell makes the refactor obvious.