The SOLID principles are a set of design guidelines that help developers create maintainable, scalable, and robust software. These principles are essential in Object-Oriented Programming (OOP) and system design, making code easier to understand and modify.
Each principle addresses specific design challenges and ensures a clean, modular codebase.
A class should have only one reason to change. This means each class should focus on one task or responsibility.
class Employee {
private String name;
private String role;
public Employee(String name, String role) {
this.name = name;
this.role = role;
}
public void calculateSalary() {
// Logic to calculate salary
System.out.println("Calculating salary for " + name);
}
public void generateReport() {
// Logic to generate a report
System.out.println("Generating report for " + name);
}
}
Here, the Employee
class is handling multiple responsibilities: salary calculation and report generation. This violates SRP.
Now suppose if I have to change calculateSalary() and generateReport() together then it violates the rule , you should change class only for a single reason or just one change you should allow in a class
class Employee {
private String name;
private String role;
public Employee(String name, String role) {
this.name = name;
this.role = role;
}
public String getName() {
return name;
}
public String getRole() {
return role;
}
}
class SalaryCalculator {
public void calculateSalary(Employee employee) {
System.out.println("Calculating salary for " + employee.getName());
}
}
class ReportGenerator {
public void generateReport(Employee employee) {
System.out.println("Generating report for " + employee.getName());
}
}
Employee
class now focuses only on representing an employee.SalaryCalculator
and ReportGenerator
) handle their respective responsibilities.Classes should be open for extension but closed for modification. This means you can add new functionality without changing existing code.
class PaymentProcessor {
public void processPayment(String paymentType) {
if (paymentType.equals("CreditCard")) {
System.out.println("Processing credit card payment");
} else if (paymentType.equals("PayPal")) {
System.out.println("Processing PayPal payment");
}
}
}
Adding a new payment method requires modifying the processPayment
method, violating OCP.
interface PaymentMethod {
void processPayment();
}
class CreditCardPayment implements PaymentMethod {
@Override
public void processPayment() {
System.out.println("Processing credit card payment");
}
}
class PayPalPayment implements PaymentMethod {
@Override
public void processPayment() {
System.out.println("Processing PayPal payment");
}
}
class PaymentProcessor {
public void processPayment(PaymentMethod paymentMethod) {
paymentMethod.processPayment();
}
}
BitcoinPayment
) only requires creating a new class that implements PaymentMethod
.A subclass should be substitutable for its superclass without altering the correctness of the program.
class Bird {
public void fly() {
System.out.println("Flying");
}
}
class Penguin extends Bird {
@Override
public void fly() {
throw new UnsupportedOperationException("Penguins can't fly");
}
}
Here, substituting Penguin
for Bird
would cause errors, violating LSP.
class Bird {
}
class FlyingBird extends Bird {
public void fly() {
System.out.println("Flying");
}
}
class Penguin extends Bird {
public void swim() {
System.out.println("Swimming");
}
}
FlyingBird
is a separate class for birds that can fly.Penguin
no longer inherits the fly
behavior, adhering to LSP.A class should not be forced to implement methods it does not use.
interface Worker {
void work();
void eat();
}
class Robot implements Worker {
@Override
public void work() {
System.out.println("Working");
}
@Override
public void eat() {
throw new UnsupportedOperationException("Robots don't eat");
}
}
interface Workable {
void work();
}
interface Eatable {
void eat();
}
class Human implements Workable, Eatable {
@Override
public void work() {
System.out.println("Working");
}
@Override
public void eat() {
System.out.println("Eating");
}
}
class Robot implements Workable {
@Override
public void work() {
System.out.println("Working");
}
}
Workable
and Eatable
) ensure that classes only implement what they need.High-level modules should not depend on low-level modules. Both should depend on abstractions.
class MySQLDatabase {
public void connect() {
System.out.println("Connecting to MySQL database");
}
}
class Application {
private MySQLDatabase database;
public Application() {
this.database = new MySQLDatabase();
}
public void start() {
database.connect();
}
}
interface Database {
void connect();
}
class MySQLDatabase implements Database {
@Override
public void connect() {
System.out.println("Connecting to MySQL database");
}
}
class PostgreSQLDatabase implements Database {
@Override
public void connect() {
System.out.println("Connecting to PostgreSQL database");
}
}
class Application {
private Database database;
public Application(Database database) {
this.database = database;
}
public void start() {
database.connect();
}
}
// Usage
public class Main {
public static void main(String[] args) {
Database database = new MySQLDatabase();
Application app = new Application(database);
app.start();
}
}
Application
class depends on the Database
abstraction, allowing the database implementation to be swapped easily.The SOLID principles are foundational for creating clean and maintainable code. By applying these principles, you can build robust, scalable, and maintainable systems that are easy to extend and modify. Happy coding ! ❤️