top of page

SOLID Principles in Programming

SOLID principles are object-oriented design concepts relevant to software development. SOLID principles form the fundamental guidelines for building object-oriented applications that are robust, extensible, and maintainable.


The SOLID principles were introduced by Robert C. Martin in his 2000 paper “Design Principles and Design Patterns.” These concepts were later built upon by Michael Feathers, who introduced us to the SOLID acronym. And in the last 20 years, these five principles have revolutionized the world of object-oriented programming, changing the way that we write software.


SOLID is an acronym for five other class-design principles:

  1. S -Single Responsibility Principle,

  2. O - Open-Closed Principle,

  3. L - Liskov Substitution Principle,

  4. I - Interface Segregation Principle, and

  5. D - Dependency Inversion Principle.


1. Single responsibility principle

Every class in Java should have a single job to do. To be precise, there should only be one reason to change a class. Here’s an example of a Java class that does not follow the single responsibility principle (SRP):

public class Vehicle {
    public void printDetails() {}
    public double calculateValue() {}
    public void addVehicleToDB() {}
}

The Vehicle class has three separate responsibilities: reporting, calculation, and database. By applying SRP, we can separate the above class into three classes with separate responsibilities.


Advantages:

  1. The class is easier to understand - When the class only does “one thing”, its interface usually has a small number of methods that are fairly self explanatory. It should also have a small number of member variables (less than seven-ish).

  2. The class is easier to maintain - Changes are isolated, reducing the chance of breaking other unrelated areas of the software. As programming errors are inversely proportional to complexity, being easier to understand makes the code less prone to bugs.

  3. The class is more reusable - If a class has multiple responsibilities, and only one of those is needed in another area of the software, then the other unnecessary responsibilities hinder reusability. Having a single responsibility means the class should be reusable without modification.


Disadvantages:

  1. It's very difficult for the other developers (in other words developers unfamiliar with the class) to understand the class/function.

  2. It's difficult for the other developers to maintain the class/function or change the class/function.

  3. Writing test cases for the class/function also becomes difficult.


2. Open-closed principle

Software entities (e.g., classes, modules, functions) should be open for an extension, but closed for modification.

Consider the below method of the class VehicleCalculations:

public class VehicleCalculations {
    public double calculateValue(Vehicle v) {
        if (v instanceof Car) {
            return v.getValue() * 0.8;
        if (v instanceof Bike) {
            return v.getValue() * 0.5;

    }
}

Suppose we now want to add another subclass called Truck. We would have to modify the above class by adding another if statement, which goes against the Open-Closed Principle.

A better approach would be for the subclasses Car and Truck to override the calculateValue method:


public class Vehicle {
    public double calculateValue() {...}
}
public class Car extends Vehicle {
    public double calculateValue() {
        return this.getValue() * 0.8;
}
public class Truck extends Vehicle{
    public double calculateValue() {
        return this.getValue() * 0.9;
}

Adding another Vehicle type is as simple as making another subclass and extending from the Vehicle class.


Disadvantage:

  1. Since a class or function always allows the addition of new logic, whenever new logic is added it is always necessary to test for full functionality. That requires the addition of a new test case for the added functionality and might also require the modification of an existing test case that fails because of added functionality.

  2. It also breaks the Single Responsibility Principle since a class or function might end up doing multiple tasks.

  3. Class or function maintenance becomes difficult since a class or function can become thousands of lines of code that is difficult to understand.



3. Liskov substitution principle

The Liskov Substitution Principle (LSP) applies to inheritance hierarchies such that derived classes must be completely substitutable for their base classes.

Consider a typical example of a Square derived class and Rectangle base class:

public class Rectangle {
    private double height;
    private double width;
    public void setHeight(double h) { height = h; }
    public void setWidht(double w) { width = w; }
    ...
}
public class Square extends Rectangle {
    public void setHeight(double h) {
        super.setHeight(h);
        super.setWidth(h);
    }
    public void setWidth(double w) {
        super.setHeight(w);
        super.setWidth(w);
    }
}

The above classes do not obey LSP because you cannot replace the Rectangle base class with its derived class Square. The Square class has extra constraints, i.e., the height and width must be the same. Therefore, substituting Rectangle with Square class may result in unexpected behavior.


Disadvantages:

  1. Developed code throws a run time error or exception or also might not work as expected and that leads to program failure or incorrect results.

  2. The preceding discussion shows an example of throwing an exeption by a child class for a not supported method.



4. Interface segregation principle

The Interface Segregation Principle (ISP) states that clients should not be forced to depend upon interface members they do not use. In other words, do not force any client to implement an interface that is irrelevant to them.

Suppose there’s an interface for vehicle and a Bike class:

public interface Vehicle {
    public void drive();
    public void stop();
    public void refuel();
    public void openDoors();
}
public class Bike implements Vehicle {

    // Can be implemented
    public void drive() {...}
    public void stop() {...}
    public void refuel() {...}
    
    // Can not be implemented
    public void openDoors() {...}
}

As you can see, it does not make sense for a Bike class to implement the openDoors() method as a bike does not have any doors! To fix this, ISP proposes that the interfaces be broken down into multiple, small cohesive interfaces so that no class is forced to implement any interface, and therefore methods, that it does not need.


Disadvantage:

  1. Deliver big fat interface that forces the client to implement a method that is not required.

  2. The client ends up implementing a usefuless method, in other words a method that has no meaning to the client. This decreases the readability of the code and also confuses the developer using the client code.

  3. The client interface ends up violating SRP sometime since it might perform some action that is not related to it.


5. Dependency inversion principle

The Dependency Inversion Principle (DIP) states that we should depend on abstractions (interfaces and abstract classes) instead of concrete implementations (classes). The abstractions should not depend on details; instead, the details should depend on abstractions.

Consider the example below. We have a Car class that depends on the concrete Engine class; therefore, it is not obeying DIP.

public class Car {
    private Engine engine;
    public Car(Engine e) {
        engine = e;
    }
    public void start() {
        engine.start();
    }
}
public class Engine {
   public void start() {...}
}

The code will work, for now, but what if we wanted to add another engine type, let’s say a diesel engine? This will require refactoring the Car class.

However, we can solve this by introducing a layer of abstraction. Instead of Car depending directly on Engine, let’s add an interface:

public interface EngineInterface {
    public void start();
}

Now we can connect any type of Engine that implements the Engine interface to the Car class:

public class Car {
    private EngineInterface engine;
    public Car(EngineInterface e) {
        engine = e;
    }
    public void start() {
        engine.start();
    }
}
public class PetrolEngine implements EngineInterface {
   public void start() {...}
}
public class DieselEngine implements EngineInterface {
   public void start() {...}
}

Disadvantage:

  1. The testability of the module becomes difficult.

  2. Parallel development of the module becomes difficult.

  3. Many changes are required when there is modification in the module and when there are changes in the module it depends on.



Benefits of SOLID Principle:

The following are the five advantages of the SOLID principle when incorporated in your software design process:


1. Accessibility

The SOLID Principle ensures easy access and control to object entities. The integrity of stable object-oriented applications provides easy access to objects, eliminating the risks of unintended inheritance.


2. Ease of refactoring

Software change over time. Therefore developers need to build applications while keeping in mind the possibility of future changes. Poorly structured software applications make it difficult to refactor, but it is quite effortless to refactor your codebase with the SOLID principle.


3. Extensibility

Software go through phases of upgrades, including extra features. If extending the features of an application is not done tactfully, this could affect existing functionalities and cause unintended problems.


The procedure of extensibility could be a gruesome process, as you need to build on functionalities that already exist in the codebase, and if the existing functionalities are not adequately designed, this makes it even more difficult to add extra features. But the application of SOLID principles makes the extensibility process easier.


4. Debugging

Debugging is a crucial part of the software development process. When software applications are not adequately designed, and the codebase is clustered like spaghetti, it is hard to debug applications. The SOLID principle incorporates the feature of ensuring that the debugging process of software is much more comfortable.


5. Readability

A well-designed codebase can be easy to understand and read. Readability is also an essential element in software development because it makes the debugging and refactoring operations easier, especially in open-source projects. The SOLID principle approach ensures that your code is relatively easier to read and understand.



The Tech Platform

0 comments
bottom of page