Encapsulation protects invariants.
Abstraction protects architecture.
If encapsulation controls state,
abstraction controls dependency.
And without it, your system slowly turns into a fragile web of concrete implementations.
A Brief Historical Context
Abstraction became critical when software systems stopped being small.
In early OOP systems, objects communicated directly with concrete implementations.
But as systems grew:
- Infrastructure changed
- Databases evolved
- APIs were replaced
- Vendors switched
Hard-coded dependencies became the biggest source of rigidity.
Abstraction emerged as a way to:
Depend on behavior contracts, not implementations.
That single idea made large systems survivable.
What Abstraction Really Means
Abstraction is:
- Defining behavior without exposing implementation
- Programming against contracts
- Isolating high-level logic from low-level details
- Reducing coupling
Abstraction is not:
- Just creating interfaces everywhere
- Adding layers for no reason
- Over-engineering small systems
Abstraction is about managing volatility.
❌ The Problem: Concrete Coupling
Let’s say we’re building a payment service.
final class OrderService
{
public function pay(float $amount): void
{
$stripe = new StripePaymentGateway();
$stripe->charge($amount);
}
}
What’s wrong?
- OrderService depends directly on Stripe
- Impossible to switch provider without editing business logic
- Hard to test
- Violates dependency inversion
- Infrastructure leaks into domain logic
This is tight coupling.
Why This Becomes Dangerous
Imagine:
- Stripe increases fees
- You must support PayPal
- A region requires a local provider
- Stripe API changes
Now you must modify core logic.
Your domain is polluted by infrastructure decisions.
That’s architectural fragility.
✅ Introducing Abstraction
We define a contract.
interface PaymentGateway
{
public function charge(float $amount): void;
}
Now we create implementations.
final class StripePaymentGateway implements PaymentGateway
{
public function charge(float $amount): void
{
// Call Stripe API
}
}
final class PaypalPaymentGateway implements PaymentGateway
{
public function charge(float $amount): void
{
// Call PayPal API
}
}
Now we depend on abstraction.
Refactoring the Service
final class OrderService
{
public function __construct(
private PaymentGateway $paymentGateway
) {}
public function pay(float $amount): void
{
$this->paymentGateway->charge($amount);
}
}
Now:
✔ OrderService does not know about Stripe
✔ Infrastructure can change
✔ Testing becomes trivial
✔ Domain logic is protected
This is abstraction.
Abstraction and Dependency Direction
The critical rule:
High-level modules should not depend on low-level modules.
Both should depend on abstractions.
This is the foundation of the Dependency Inversion Principle.
Your domain should never depend on Stripe.
Stripe should depend on your abstraction.
Architectural Impact
With abstraction:
- You can switch providers
- You can mock dependencies
- You isolate external volatility
- You maintain stable core logic
Without abstraction:
- Refactoring becomes dangerous
- Tests require real infrastructure
- Every change ripples across the system
Advanced Insight: Abstraction is About Volatility Isolation
Ask yourself:
What changes frequently?
- External APIs
- Databases
- Message brokers
- File storage
- Email providers
These are volatile dependencies.
Your core business rules are stable.
Abstraction isolates stable from unstable.
When Abstraction Is Overkill
Abstraction adds indirection.
Do not abstract:
- Simple scripts
- Tiny applications
- One-off utilities
- Stable internal code with no variability
Premature abstraction creates:
- Interface pollution
- Unnecessary complexity
- Cognitive overhead
Abstraction should follow variability, not fear.
Abstraction vs Encapsulation
Encapsulation protects internal state.
Abstraction protects dependency boundaries.
Encapsulation works inside the object.
Abstraction works between objects.
Together, they define structural integrity.
Common Anti-Patterns
1️⃣ Interface Per Class
interface UserServiceInterface {}
class UserService implements UserServiceInterface {}
No variability.
No alternative implementation.
Just noise.
2️⃣ Leaky Abstractions
interface PaymentGateway
{
public function chargeWithStripeToken(string $token): void;
}
Stripe-specific logic inside abstraction.
That defeats the purpose.
Final Insight
Abstraction is not about hiding code.
It is about controlling dependency direction.
If encapsulation prevents internal chaos,
abstraction prevents architectural collapse.
Abstraction allows your system to evolve without rewriting its core.
That is why it is the second pillar of OOP.
Thanks for reading!
If you have any questions, complaints or tips, you can leave them here in the comments. I will be happy to answer!
😊😊 See you! 😊😊
Support Me
Youtube - WalterNascimentoBarroso
Github - WalterNascimentoBarroso
Codepen - WalterNascimentoBarroso
Top comments (0)