Low-level design2 min
Abstraction
Abstraction is the process of hiding the complex internal details of a system and exposing only the essential features to the user. It helps in reducing programming complexity and effort.
While Encapsulation is about hiding data for security and control, Abstraction is about hiding complexity to make a system easier to use.
Real-Life Analogy
Think of a car's steering wheel and pedals. To drive a car, you only need to know how to turn the steering wheel, press the accelerator, and press the brake. You do not need to understand how the fuel injector works, how the transmission shifts gears, or how the combustion engine fires.
The complex internal machinery is abstracted away behind a simple, intuitive interface.
How to Implement Abstraction
In OOP, abstraction is typically achieved using Abstract Classes and Interfaces.
- Abstract Class: A class that cannot be instantiated directly and may contain both implemented methods and abstract (unimplemented) methods.
- Interface: A pure contract that only contains method signatures (no implementation). Any class that implements the interface must provide the code for those methods.
Example using an Interface
// The Abstraction: A simple interface that hides complexity
interface PaymentProcessor {
processPayment(amount: number): boolean;
}
// Complex Implementation 1
class StripeProcessor implements PaymentProcessor {
public processPayment(amount: number): boolean {
// 100 lines of complex code connecting to Stripe APIs,
// handling webhooks, generating cryptographic signatures...
console.log(`Processing $${amount} securely via Stripe.`);
return true;
}
}
// Complex Implementation 2
class PayPalProcessor implements PaymentProcessor {
public processPayment(amount: number): boolean {
// 100 lines of complex code connecting to PayPal APIs,
// handling OAuth tokens...
console.log(`Processing $${amount} securely via PayPal.`);
return true;
}
}
// The Client Code only interacts with the Abstraction
class CheckoutService {
private processor: PaymentProcessor;
// We inject the abstraction, hiding the concrete details
constructor(processor: PaymentProcessor) {
this.processor = processor;
}
public checkout(total: number): void {
// The complexity is completely hidden from the CheckoutService
this.processor.processPayment(total);
}
}
const service = new CheckoutService(new StripeProcessor());
service.checkout(150);
Why is it Important?
- Reduced Complexity: Developers using your class only need to understand its public interface, not its entire internal codebase.
- Loose Coupling: By depending on abstractions (interfaces) rather than concrete implementations, you decouple your system. You can swap a
StripeProcessorfor aPayPalProcessorwithout changing theCheckoutServiceat all.