Demystifying the SOLID Principles: A Guide for New Programmers

Have you ever heard the acronym “SOLID” used in reference to object-oriented programming?  SOLID refers to a set of five principles for writing object-oriented software.  The acronym comes to us from Robert “Uncle Bob” Martin, and acts as a guide for designing and organizing object-oriented code in such a way that the resulting applications are understandable, flexible, and scalable.  In this article, we’ll take a look at the five principles of SOLID and see how we can apply them to follow best practices for object-oriented programming.

S: The Single Responsibility Principle (SRP)

The Single Responsibility Principle states that “a class should have only one reason to change”.  This means that each class in your application should do one job only, and do it well.  Each class in your program needs to encapsulate a single concept. 

O: The Open/Closed Principle (OCP)

The Open/Closed Principle suggests that classes, modules, and functions should be “open for extension, but closed for modification”–for example, you should be able to add new functionality to a class without altering any existing code, usually through inheritance or by implementing an interface. 

L: The Liskov Substitution Principle (LSP) 

Named for Barbara Liskov, the Liskov Substitution Principle states that objects of a subclass should be able to replace objects of their superclass without affecting the correctness of the application.  In other words, subclasses should be extensions of their base class, and replacing an instance of the base class with any of its subclasses should not cause any unexpected behavior in a program. 

I: The Interface Segregation Principle (ISP)

The Interface Segregation Principle advocates that clients should not be forced to depend on interfaces they do not use.  In other words: it’s better to have smaller interfaces that make it easier for classes to implement only methods that are relevant to their specific responsibilities, instead of large interfaces that force classes to implement methods they don’t need.

D: The Dependency Inversion Principle (DIP) 

The Dependency Inversion Principle states that “high-level” modules–those that manage business logic or orchestrate application flow–should not depend on “low-level” modules–those that are concerned with implementation details, external services, or program infrastructure.  Instead, high-level modules should depend on abstractions of low-level specifics.  The DIP encourages the use of interfaces and abstract classes to define relationships between components in a program, which allows for greater flexibility in selecting and using specific implementations.  If you’ve ever used something like the DAO design pattern, you’ve followed the Dependency Inversion Principle. 

Conclusion 

Consider this Java class that represents a checking account.  Which of the SOLID principles do you think it breaks? 

public class CheckingAccount {

   private int accountNumber;

   private double balance;

   private String url = "jdbc:postgres://localhost:5432/bankingdata";

   private String username = "db_user";

   private String password = "db_password";


   public BankAccount(int accountNumber, double balance) {

      this.accountNumber = accountNumber;
  
      this.balance = balance;
   }


   public void deposit(double amount) {

      balance += amount;

      Connection conn = null; 

      PreparedStatement statement = null; 

      ResultSet result = null;

      try {

          conn = DriverManager.getConnection(url, username, password);

          String sqlQuery = "UPDATE checking SET balance = ? WHERE account = ?";

          statement = conn.preparedStatement(sqlQuery);

          preparedStatement.setString(1, this.balance);

          preparedStatement.setDouble(2, this.accountNumber);

          result = preparedStatement.executeUpdate();
      }

      catch(SQLException sqlEx) {
          System.out.println("There was an error communicating with the database.");
      }

      catch(Exception ex) {
          System.out.println("A general error occurred.")
      }

      finally { 
          try { result.close(); } catch (Exception e) { /* continue */ } 
          try { statement.close(); } catch (Exception e) { /* continue */}  
          try { conn.close(); } catch (Exception e) { /* continue */ }
      }
   }
}

Ready for an answer?  If you came up with the “S”, “O”, and “D” out of SOLID, well done!

  • This class violates the Single Responsibility Principle by doing more than one thing.  It doesn’t just keep track of the account balance, it also manages a database connection and updates the database when the balance changes!  A class should only be responsible for one thing–either keeping track of the account balance, or interacting with the database.
  • This class violates the Open/Closed Principle by not being closed for modification.  If you ever switched out the PostgreSQL database for another type of database, you would have to modify this class directly–and if you switched the relational database out for another type of datastore entirely, you may have to heavily edit the checking account class.  
  • This class violates the Dependency Inversion Principle because it includes details about the database, database connection, and database operations, which tightly couples the database interaction and the business logic of the class.  Instead, the checking account class should depend on an interface that abstracts away the details of the application’s data access, so that the class doesn’t concern itself with the intimate details of interacting with the datastore.  

Thinking through this example, hopefully you realized that keeping the SOLID principles in mind can help you write clean, coherent code that is easily maintainable.  Next time you’ve finished writing a feature, take another look at the code you’ve created and try to find any places where you may have violated one of the five principles.  Using the SOLID acronym and keeping these five maxims in mind will help keep you in line with best practices in object-oriented programming and ensure that the systems you create are resilient, adaptable, and understandable to other programmers.  Happy coding!

Add a Comment