Object-Oriented Design (OOD) is a paradigm that uses "objects" – instances of classes – to design applications and computer programs. This approach organizes software design around data, or objects, rather than functions and logic.
OOD principles help create software that is:
Before diving into principles, understanding the fundamental concepts is crucial:
The SOLID principles are a set of five design principles intended to make software designs more understandable, flexible, and maintainable.
A class should have only one reason to change. This means that a class should have a single, well-defined job. If a class has multiple responsibilities, it becomes harder to maintain and test.
Example: Instead of a `Report` class that generates a report, formats it, and sends it via email, you would have separate classes like `ReportGenerator`, `ReportFormatter`, and `EmailSender`.
Software entities (classes, modules, functions, etc.) should be open for extension, but closed for modification. This means you should be able to add new functionality without altering existing code.
Example: Using interfaces or abstract classes allows you to create new implementations that extend the behavior of existing code without changing the base classes.
// Instead of modifying this
class DiscountCalculator {
calculate(price, type) {
if (type === 'regular') {
return price * 0.9;
} else if (type === 'premium') {
return price * 0.8;
}
return price;
}
}
// Use this with extensions
interface DiscountStrategy {
applyDiscount(price: number): number;
}
class RegularDiscount implements DiscountStrategy {
applyDiscount(price: number): number {
return price * 0.9;
}
}
class PremiumDiscount implements DiscountStrategy {
applyDiscount(price: number): number {
return price * 0.8;
}
}
class DiscountCalculator {
private strategy: DiscountStrategy;
constructor(strategy: DiscountStrategy) {
this.strategy = strategy;
}
calculate(price: number): number {
return this.strategy.applyDiscount(price);
}
}
Objects of a superclass should be replaceable with objects of its subclasses without altering the correctness of the program. Subtypes must be substitutable for their base types.
Example: If you have a `Bird` class with a `fly()` method, a `Penguin` class (which is a subtype of `Bird`) should not override `fly()` with an exception or no-op. Instead, `Penguin` might not inherit from `Bird` or might have a different interface.
Clients should not be forced to depend on interfaces they do not use. It is better to have many small, client-specific interfaces than one large, general-purpose interface.
Example: Instead of a single `Worker` interface with `work()` and `eat()` methods, you might have `Workable` and `Eatable` interfaces, allowing different types of workers to implement only what they need.
High-level modules should not depend on low-level modules. Both should depend on abstractions. Abstractions should not depend on details. Details should depend on abstractions.
Example: A `ReportGenerator` (high-level) should not directly depend on a `DatabaseLogger` (low-level). Instead, both should depend on an `ILogger` interface.
// Low-level module
class DatabaseLogger {
log(message: string): void {
console.log(`[DB] ${message}`);
}
}
// High-level module depending on low-level directly (BAD)
class OrderProcessorBad {
private logger: DatabaseLogger;
constructor() {
this.logger = new DatabaseLogger(); // Tight coupling
}
processOrder(orderId: string) {
this.logger.log(`Processing order: ${orderId}`);
// ... processing logic
}
}
// Abstraction
interface ILogger {
log(message: string): void;
}
// High-level module depending on abstraction
class OrderProcessorGood {
private logger: ILogger;
constructor(logger: ILogger) {
this.logger = logger; // Dependency Injection
}
processOrder(orderId: string) {
this.logger.log(`Processing order: ${orderId}`);
// ... processing logic
}
}
// Usage
const dbLogger = new DatabaseLogger();
const orderProcessor = new OrderProcessorGood(dbLogger);
orderProcessor.processOrder("ORD123");
Next: Introduction to Design Patterns
Previous: Programming Paradigms Overview