After understanding composition, inheritance, and polymorphism, the next important step is learning how to define clear behavioral contracts between components.
This is where interfaces and abstract classes become essential.
They help answer one of the most important questions in system design:
“What should this component be capable of doing?”
without tightly coupling the system to a specific implementation.
Why Contracts Matter in System Design
In real systems:
- multiple implementations exist
- components evolve independently
- implementations may change over time
If systems depend directly on concrete classes:
- flexibility decreases
- testing becomes difficult
- coupling increases
Contracts solve this problem.
What is an Interface?
An interface defines:
A capability contract that implementing classes must follow.
It specifies:
- what operations are available
- not how they are implemented
Example
class Payment:
def pay(self):
pass
Any implementation must provide:
pay()
But implementation details remain independent.
Core Idea
Interfaces define capability expectations, not implementation details.
What is an Abstract Class?
An abstract class provides:
Partial implementation along with abstract behavior contracts.
It is useful when:
- some logic is common across implementations
- some behavior varies by subclass
Example
class Notification:
def log(self):
print("Logging notification")
def send(self):
pass
Here:
-
log()→ shared behavior -
send()→ implementation-specific behavior
Interface vs Abstract Class
| Interface | Abstract Class |
|---|---|
| defines contract | defines partial behavior |
| focuses on capability | focuses on shared structure |
| minimal implementation | can contain implementation |
| enables loose coupling | enables reuse + extension |
Key Mental Model
Think of it this way:
Interface
“What can this object do?”
Abstract Class
“What common behavior already exists?”
Real System Example
Payment System
Contract
class Payment:
def pay(self):
pass
Implementations
class CardPayment(Payment):
def pay(self):
print("Processing card payment")
class UpiPayment(Payment):
def pay(self):
print("Processing UPI payment")
Usage
def process(payment: Payment):
payment.pay()
Now the system depends on:
- abstraction not:
- concrete implementation
Why Interfaces Matter in LLD
Interfaces help achieve:
1. Loose Coupling
Components communicate through contracts instead of implementations.
2. Extensibility
New implementations can be added safely.
3. Testability
Mock implementations become easy.
Example:
- MockPayment
- FakeNotificationService
4. System Scalability
Independent teams can build implementations separately while following the same contract.
Why Abstract Classes Matter
Abstract classes help when:
- logic is partially shared
- duplication needs reduction
- related classes have common behavior
They provide:
- controlled reuse
- shared structure
Common Mistake
Beginners often:
- create interfaces for everything
- or avoid abstractions entirely
Both create problems.
Over-Abstraction
Too many unnecessary interfaces lead to:
- complexity
- indirection
- unreadable systems
Under-Abstraction
No contracts lead to:
- tight coupling
- rigid systems
- difficult testing
Design Principle
Depend on abstractions for flexibility, but introduce abstractions only when variation or decoupling is actually needed.
Real-World Analogy
Think of a charging socket.
The socket defines:
- voltage standard
- connection contract
Different chargers:
- Apple
- Samsung
- Laptop adapters
all work because they follow the same contract.
The socket does not care about internal implementation.
One-Line Takeaway
Interfaces define what a component must be capable of doing, while abstract classes help share common behavior across related implementations.
Top comments (0)