17 Feb 2020

SOLID

SOLID principles of programming are a set of five design principles that help developers create code that is easy to maintain and extend over time.

Single Responsibility Principle

  • every entity should have only a single responsibility
  • a class should have only one reason to change

Open Closed Principle

  • the code should be open for extending
  • the code should be closed for changing

You should be able to extend the behaviour of a class without modifying its existing code.

For example, instead of modifying the existing code of a class to add new functionality, you can create a new derived class that extends the behaviour of the base class.

Liskov substitution principle

  • Objects of a superclass should be able to be replaced with objects of its subclasses without affecting the correctness of the program.
  • Ensures that the inheritance hierarchy’s behaviour of the classes is consistent and predictable.

For example:

  1. Base class Shape
  2. Subclass called Rectangle.

We should be able to replace an instance of Shape with an instance of Rectangle without affecting the correctness of the program. Any code that works with a Shape should also work correctly with a Rectangle.

Interface Segregation Principle

  • Clients should not be forced to depend on interfaces they do not use.
  • Many specific interfaces are better than one general interface.

For example, instead of having a single large interface that contains many methods, create smaller interfaces that each client can implement based on their specific requirements.

This helps to prevent clients from being burdened with unnecessary dependencies.

Dependency Inversion Principle

High-level modules should not directly depend on low-level modules. Instead, both high-level and low-level modules should depend on abstractions (interfaces or abstract classes).

For example, here we have a high-level class called ReportGenerator and a low-level class called DatabaseConnection. Without applying DIP, we have a direct dependency like this:

class ReportGenerator {
    private DatabaseConnection databaseConnection;

    public ReportGenerator() {
        databaseConnection = new DatabaseConnection();
    }
}

The ReportGenerator class directly creates and depends on the DatabaseConnection class. If DatabaseConnection changes it can lead to changes in the ReportGenerator class.

Now, applying DIP, we introduce an abstraction (interface) like DataStore:

interface DataStore {
    // Define methods common to data storage
}

class DatabaseConnection implements DataStore {
    // Implement the DataStore interface
}

class ReportGenerator {
    private DataStore dataStore;

    public ReportGenerator(DataStore dataStore) {
        this.dataStore = dataStore;
    }
}