SOLID Principles Explained (with examples in Java)

rachel - May 2 - - Dev Community

The SOLID principles, conceptualized by Robert C. Martin (aka Uncle Bob), is a fundamental design principles that aim to create well-structured and maintainable code. This article will walk you through the 5 principles with examples.

1. Single Responsibility Principle (SRP)

  • "A class should have only one reason to change."
  • Each class, method, or function should serve a single, well-defined purpose, with all elements within it supporting that purpose.
  • If a change needs to be made, it should only affect that single responsibility, and not other unrelated parts of the codebase.
// Violates SRP
public class BankAccount {
    private double balance;

    public void deposit(double amount) {
        balance += amount;
    }

    public void withdraw(double amount) {
        if (balance >= amount) {
            balance -= amount;
        } else {
            System.out.println("Insufficient funds");
        }
    }

    public void printBalance() {
        System.out.println("Current balance: " + balance);
    }
}

Enter fullscreen mode Exit fullscreen mode

The code above violates SRP because say if the requirement changed to display the balance in a different format, then the BankAccount class would need to be updated, hence violating SRP. To resolve this, we can separate them into 2 classes, ensuring that each class has a single responsibility.

// Follows SRP
public class BankAccount {
    private double balance;

    public void deposit(double amount) {
        balance += amount;
    }

    public void withdraw(double amount) {
        if (balance >= amount) {
            balance -= amount;
        } else {
            throw new IllegalArgumentException("Insufficient funds");
        }
    }

    public double getBalance() {
        return balance;
    }
}

public class BalanceDisplayer {
    public void printBalance(BankAccount account) {
        System.out.println("Current balance: " + account.getBalance());
    }
}
Enter fullscreen mode Exit fullscreen mode

2. Open closed Principle (OSP)

  • "Software components should be open for extension but closed for modification."
  • New functionality can be added without changing existing code.

Using the same example above, let's say we want to display the balance in a few formats. To modify the BalanceDisplayer class without violating OCP, we need to design the code in such a way that everyone can reuse the feature by just extending it.

// Interface for balance display
public interface BalanceDisplay {
    void displayBalance(BankAccount account);
}

// Implementation for displaying balance in a simple format
public class SimpleBalanceDisplay implements BalanceDisplay {
    @Override
    public void displayBalance(BankAccount account) {
        System.out.println("Current balance: " + account.getBalance());
    }
}

// Implementation for displaying balance in a fancy format
public class FancyBalanceDisplay implements BalanceDisplay {
    @Override
    public void displayBalance(BankAccount account) {
        System.out.println("~~~ Fancy Balance: $" + account.getBalance() + " ~~~");
    }
Enter fullscreen mode Exit fullscreen mode

3. Liskov substitution Principle (LSP)

  • "Derived or child classes must be substitutable for their base or parent classes."
  • If B is a subclass of A, B should be able to replace A without affecting the correctness of the program.
// Subclass SavingsAccount
public class SavingsAccount extends BankAccount {
    public SavingsAccount(double balance) {
        super(balance);
    }

    // Additional functionality specific to SavingsAccount
    public void calculateInterest() {
        // Calculate interest for savings account
    }
}

public class GoldAccount extends BankAccount {
    private double bonusPoints;

    public GoldAccount(double balance, double bonusPoints) {
        super(balance);
        this.bonusPoints = bonusPoints;
    }

    @Override
    public void deposit(double amount) {
        balance += amount + (bonusPoints * 0.1); // Adds bonus points to the deposit
    }
}

Enter fullscreen mode Exit fullscreen mode
  • SavingsAccount adheres to LSP as it extends the functionality by adding specific methods like calculateInterest, which do not alter the core behavior of depositing and withdrawing funds.
  • GoldAccount class violates LSP by changing the behavior of the deposit method from the base BankAccount class.

4. Interface Segregation Principle (ISP)

  • "Do not force any client to implement an interface which is irrelevant to them."
  • Clients should not be compelled to implement interfaces that contain methods they do not use.
  • Instead of having a single large interface, it is better to have multiple smaller interfaces, each focusing on a specific set of methods relevant to a particular functionality.
// Violation of ISP
public interface IBankAccount {
    void deposit(double amount);
    void withdraw(double amount);
    double getBalance();
    void printStatement();
    void requestLoan();
}

public class BankAccount implements IBankAccount {
    private double balance;

    public void deposit(double amount) {
        // implementation details
    }

    public void withdraw(double amount) {
        // implementation details
    }

    public double getBalance() {
        // implementation details
    }

    public void printStatement() {
        // implementation details
    }

    public void requestLoan() {
        // implementation details
    }
}

Enter fullscreen mode Exit fullscreen mode
  • The IBankAccount interface violates ISP by including methods that are not relevant to all classes that implement it.
  • The BankAccount class implements the entire IBankAccount interface, even though it may not need the requestLoan method.
// Adheres to ISP
// Interface for account management
public interface AccountManager {
    void deposit(double amount);
    void withdraw(double amount);
    double getBalance();
}

// Interface for account reporting
public interface AccountReporter {
    void printStatement();
}

// Interface for loan management
public interface LoanManager {
    void requestLoan();
}
Enter fullscreen mode Exit fullscreen mode

Each class now depends only on the interfaces relevant to its responsibilities, adhering to ISP.

5. Dependency Inversion Principle (DIP)

  • "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."
  • Think of it like a restaurant. The high-level module is the restaurant, and the low-level module is the kitchen. The restaurant should not directly depend on the kitchen. Instead, both should depend on a common language, like English. The kitchen should not depend on the restaurant's specific menu. Instead, the menu should depend on the kitchen's cooking skills.
// Violates DIP
public class ShoppingMall {
    private BankAccount bankAccount;

    public ShoppingMall(BankAccount bankAccount) {
        this.bankAccount = bankAccount;
    }

    public void doPayment(String order, double amount) {
        bankAccount.withdraw(amount);
        // Process the payment
    }
}
Enter fullscreen mode Exit fullscreen mode

In this example, the ShoppingMall class directly depends on the BankAccount class, which violates DIP. The ShoppingMall class is a high-level module, and the BankAccount class is a low-level module.

To fix this, we can introduce an abstraction that both the ShoppingMall and BankAccount classes can depend on.

// Adhering to DIP
public interface PaymentProcessor {
    void processPayment(double amount);
    double getBalance();
}


public class BankAccount implements PaymentProcessor {
    // implementation details
}

public class ShoppingMall {
    private PaymentProcessor paymentProcessor;

    public ShoppingMall(PaymentProcessor paymentProcessor) {
        this.paymentProcessor = paymentProcessor;
    }

    public void doPayment(String order, double amount) {
        paymentProcessor.processPayment(amount);
        // Process the payment
    }
}
Enter fullscreen mode Exit fullscreen mode

Thank you for reading this article. I hope you found it informative and that it helps you write better, more robust code in your future projects.

. . . . . . . . . . .