UML & Modeling • Class DiagramsHard⏱️ ~3 min
Class Diagram Anti-Patterns and Refactoring
Recognizing and fixing class diagram anti-patterns is a critical interview skill. Interviewers often present flawed designs and ask you to identify problems and propose solutions.
Anti-Pattern: God Class
ParkingLotManager
- spots: List
- vehicles: List
- payments: List
+ findSpot()
+ assignSpot()
+ calculateFee()
+ processPayment()
+ generateReport()
+ sendNotification()
- spots: List
- vehicles: List
- payments: List
+ findSpot()
+ assignSpot()
+ calculateFee()
+ processPayment()
+ generateReport()
+ sendNotification()
Problem: Single class with too many responsibilities violates SRP (Single Responsibility Principle)
→
Refactored: Separated Concerns
Split into:
-
-
-
-
-
Each class has one reason to change
-
ParkingLot (spot management)-
FeeCalculator (pricing logic)-
PaymentProcessor (payment handling)-
ReportGenerator (analytics)-
NotificationService (alerts)Each class has one reason to change
Anti-Pattern 1: Inappropriate Inheritance
Wrong:
Car extends ParkingSpot (Car IS-A ParkingSpot makes no sense)Right:
ParkingSpot has a currentVehicle attribute (aggregation, not inheritance)Detection: Ask "IS-A" question. "Car IS-A ParkingSpot?" No. "Car parks IN-A ParkingSpot?" Yes (use association/aggregation). If IS-A sounds wrong, don't use inheritance.
Anti-Pattern 2: Circular Dependencies
Vehicle
- spot: ParkingSpot
- spot: ParkingSpot
↔
ParkingSpot
- vehicle: Vehicle
- vehicle: Vehicle
Problem: Both classes depend on each other, making testing and modification difficult. Change Vehicle, must update ParkingSpot.
Solution: One-directional dependency.
ParkingSpot references Vehicle, but Vehicle doesn't know about spots. The ParkingLot class manages the assignment. If Vehicle needs to know its spot, use a mapping in ParkingLot instead.Anti-Pattern 3: Feature Envy
Wrong:
ParkingTicket has method calculateVehicleSize(vehicle) that accesses all Vehicle attributesRight: Move logic to
Vehicle.getSize(). ParkingTicket just calls vehicle.getSize()Detection: If a method in Class A uses more data from Class B than from Class A itself, move the method to Class B. This follows the principle "Tell, Don't Ask."
Anti-Pattern 4: Primitive Obsession
Before (Primitives)
ParkingTicket
- entryTime: Long
- exitTime: Long
- feeAmount: Double
- currency: String
- entryTime: Long
- exitTime: Long
- feeAmount: Double
- currency: String
After (Value Objects)
ParkingTicket
- duration: Duration
- fee: Money
Money {amount, currency}
- duration: Duration
- fee: Money
Money {amount, currency}
Problem: Using primitives (Double for money, String for dates) loses domain meaning and makes validation difficult. What if someone passes negative fee or invalid currency?
Solution: Create value objects (
Money, Duration) that encapsulate validation and behavior. Money ensures non-negative amounts and valid currency codes.Anti-Pattern 5: Anemic Domain Model
Symptom: Classes with only getters/setters, no business logic. All logic resides in "Manager" or "Service" classes.
Anemic (Bad)
ParkingSpot
+ getId()
+ setOccupied(bool)
SpotManager
+ assignVehicle(spot, v)
+ releaseVehicle(spot)
+ checkAvailability(spot)
+ getId()
+ setOccupied(bool)
SpotManager
+ assignVehicle(spot, v)
+ releaseVehicle(spot)
+ checkAvailability(spot)
Rich Domain (Good)
ParkingSpot
- vehicle: Vehicle
+ assignVehicle(v): bool
+ releaseVehicle(): void
+ isAvailable(): bool
- validateSize(v): bool
- vehicle: Vehicle
+ assignVehicle(v): bool
+ releaseVehicle(): void
+ isAvailable(): bool
- validateSize(v): bool
Fix: Move behavior into domain objects.
ParkingSpot knows how to assign/release vehicles. Manager classes coordinate but don't contain business logic.Interview Tip: If the interviewer presents a flawed design, don't immediately say "This is wrong." First acknowledge what works ("This structure captures the basic entities"), then gently point out issues ("However, I notice ParkingLot has many responsibilities. Could we separate concerns using...?"). This shows diplomacy and structured critique.
Refactoring Strategy:
- Identify smells: God classes, circular deps, inappropriate inheritance, primitive obsession
- Apply SOLID: SRP (split responsibilities), OCP (use polymorphism), LSP (correct inheritance), ISP (focused interfaces), DIP (depend on abstractions)
- Introduce patterns: Strategy for algorithms, State for lifecycle, Factory for creation, Observer for notifications
- Validate: Walk through scenarios to ensure refactored design still meets requirements
Practice Exercise: Take any class diagram you've created and ask: First, does any class violate SRP (more than one reason to change)? Second, are there circular dependencies (A depends on B depends on A)? Third, is inheritance used correctly (does IS-A make sense)? Fourth, are primitives used where value objects would be better? Fifth, do domain objects contain behavior or just data? Fix each issue systematically.
💡 Key Takeaways
✓God Class anti-pattern: Single class with too many responsibilities, violates SRP, fix by splitting into focused classes
✓Inappropriate inheritance: Using inheritance when composition/aggregation is correct, test with IS-A question
✓Circular dependencies: Two classes referencing each other, fix with one-directional dependency or mediator
✓Primitive obsession: Using primitives (Double, String) instead of value objects (Money, Duration) loses domain meaning
✓Anemic domain model: Classes with only getters/setters, no behavior, fix by moving logic into domain objects
📌 Examples
1God Class: ParkingLotManager doing spots, payments, reports, notifications - split into separate classes
2Wrong inheritance: Car extends ParkingSpot (nonsensical) - use ParkingSpot has Vehicle instead
3Circular dependency: Vehicle knows ParkingSpot and vice versa - make ParkingSpot know Vehicle, not reverse
4Primitive obsession: Using Double for money - create Money value object with amount and currency
5Anemic model: ParkingSpot with only getters/setters - add assignVehicle(), releaseVehicle() methods