The SOLID principles are foundational design guidelines in object-oriented programming. They help developers create scalable, maintainable, and robust systems by promoting good software design practices.
The SOLID Principles
1. Single Responsibility Principle (SRP)
Definition: A class should have one, and only one, reason to change.
- Each class should focus on a single responsibility or functionality.
Example:
Violation:
public class Invoice {
public void calculateTotal() {
// Logic to calculate total
}
public void printInvoice() {
// Logic to print invoice
}
}
Problem:
The class has two responsibilities: calculating the total and printing the invoice.
Changes in printing logic would require modifying this class, violating SRP.
Adherence:
public class Invoice {
public void calculateTotal() {
// Logic to calculate total
}
}
public class InvoicePrinter {
public void printInvoice(Invoice invoice) {
// Logic to print invoice
}
}
Benefit:
- Each class has a single responsibility, making the code easier to maintain.
2. Open/Closed Principle (OCP)
Definition: A class should be open for extension but closed for modification.
- You should be able to add new functionality without altering existing code.
Example:
Violation:
public class PaymentProcessor {
public void processPayment(String paymentType) {
if (paymentType.equals("CreditCard")) {
// Process credit card payment
} else if (paymentType.equals("PayPal")) {
// Process PayPal payment
}
}
}
Problem:
- Adding a new payment type requires modifying the
processPayment
method, which violates OCP.
Adherence:
interface Payment {
void processPayment();
}
public class CreditCardPayment implements Payment {
public void processPayment() {
// Process credit card payment
}
}
public class PayPalPayment implements Payment {
public void processPayment() {
// Process PayPal payment
}
}
public class PaymentProcessor {
public void processPayment(Payment payment) {
payment.processPayment();
}
}
Benefit:
- Adding a new payment type only requires creating a new implementation of the
Payment
interface.
3. Liskov Substitution Principle (LSP)
Definition: Subtypes must be substitutable for their base types.
- Derived classes should be able to replace base classes without affecting functionality.
Example:
Violation:
public class Bird {
public void fly() {
// Fly logic
}
}
public class Penguin extends Bird {
@Override
public void fly() {
throw new UnsupportedOperationException("Penguins can't fly!");
}
}
Problem:
- A
Penguin
is not substitutable forBird
, violating LSP.
Adherence:
public interface Bird {
void eat();
}
public interface FlyingBird extends Bird {
void fly();
}
public class Sparrow implements FlyingBird {
@Override
public void fly() {
// Fly logic
}
@Override
public void eat() {
// Eat logic
}
}
public class Penguin implements Bird {
@Override
public void eat() {
// Eat logic
}
}
Benefit:
- The
Penguin
andSparrow
classes adhere to their respective responsibilities without violating LSP.
4. Interface Segregation Principle (ISP)
Definition: A class should not be forced to implement interfaces it does not use.
- Split large interfaces into smaller, specific ones.
Example:
Violation:
public interface Animal {
void fly();
void swim();
void run();
}
public class Dog implements Animal {
@Override
public void fly() {
throw new UnsupportedOperationException();
}
@Override
public void swim() {
// Swim logic
}
@Override
public void run() {
// Run logic
}
}
Adherence:
public interface Flyable {
void fly();
}
public interface Swimmable {
void swim();
}
public interface Runnable {
void run();
}
public class Dog implements Runnable, Swimmable {
@Override
public void swim() {
// Swim logic
}
@Override
public void run() {
// Run logic
}
}
Benefit:
- Each class implements only the behaviors it needs.
5. Dependency Inversion Principle (DIP)
Definition: Depend on abstractions, not on concrete implementations.
Example:
Violation:
public class Keyboard {
// Keyboard-specific logic
}
public class Computer {
private Keyboard keyboard = new Keyboard();
// Computer-specific logic
}
Problem:
- The
Computer
class is tightly coupled with theKeyboard
implementation.
Adherence:
public interface InputDevice {
void input();
}
public class Keyboard implements InputDevice {
@Override
public void input() {
// Keyboard-specific logic
}
}
public class Computer {
private InputDevice inputDevice;
public Computer(InputDevice inputDevice) {
this.inputDevice = inputDevice;
}
// Computer-specific logic
}
Benefit:
- The
Computer
class is flexible and can work with anyInputDevice
implementation.
Conclusion
The SOLID principles are essential for creating software that is easy to maintain, extend, and scale. By adhering to these principles, we can reduce technical debt and improve code quality across projects.
Feel free to share your thoughts or add examples in the comments section below!