Abstraction Trade-offs: When Interfaces Are Overkill
While abstraction promotes flexibility, overuse creates unnecessary complexity. Understanding when to apply abstraction versus when concrete classes suffice is crucial for practical design.
IParkingSpot interface with single ParkingSpot implementationParkingSpot class directlyWhen Abstraction Is Valuable:
First, multiple implementations exist or are anticipated. If you have CreditCardPayment, CashPayment, and plan to add CryptoPayment, the PaymentMethod interface is justified. The abstraction cost is offset by flexibility.
Second, testing requires mocking. A NotificationService interface allows substituting EmailNotification with MockNotification during tests without changing dependent classes. This is valuable even with a single production implementation.
Third, the system needs runtime pluggability. A PricingStrategy interface allows swapping between HourlyPricing, FlatRatePricing, and DynamicPricing without recompilation, enabling configuration-driven behavior.
When Concrete Classes Suffice:
First, the implementation is stable and unlikely to vary. A ParkingSpot class representing a physical parking space needs no interface. Creating IParkingSpot adds complexity without enabling any flexibility, as parking spots do not have alternative implementations.
Second, the class is a pure data holder (DTO). A ParkingTicket containing entry time, exit time, and spot number is a value object. Abstracting it provides no benefit, but if the ticket were to have behavior like calculateDuration() with different calculation strategies, then abstraction becomes appropriate.
Third, performance is critical and virtual dispatch overhead matters. In high-frequency trading systems or game engines, interface calls introduce minor overhead. Use concrete classes when microseconds matter, but recognize this is rarely the bottleneck in typical applications.
Common Pitfall: The "just in case" abstraction. Adding interfaces because implementations "might" vary in the future violates YAGNI (You Aren't Gonna Need It). Abstract when you have evidence of variation (current or planned), not speculation. If uncertainty exists, favor concrete classes initially and refactor to interfaces when the second implementation appears. The refactoring cost is lower than maintaining unused abstractions.