SOLID Principles In Object-oriented Programming? Watch now simple Examples

SOLID Principles
Share on facebook
Share on twitter
Share on linkedin
Share on email
Share on whatsapp
Share on pinterest
Share on print

SOLID Principles in Object-oriented Programming is five application design and development concepts of Object-oriented Programming. Robert C. Martin changes the object-oriented programming design structure by introducing SOLIC design principles. Those who follow SOLID principles help to debug, create modules, decouple the components, be easy to understand or maintain, easy to extend, and so on.

What are the SOLID Principles in Object-oriented Programming?

There are five design principles introduced by Robert C. Martin. Those are as follows in the list

  1. Single Responsibility Principle
  2. Open-Closed Principle
  3. Liskov Substitution Principle
  4. Interface Segregation Principle
  5. Dependency Inversion Principle

Let’s go through each principle and try to understand how those are used in Object-Oriented Programming. Those principles explain using JAVA language and images used to explain the concept of the principle.

Single Responsibility Principle (SRP)

Definition: A class should have one, and only one, reason to change.

The Single Responsibility Principle means every single class in the program should cover a single requirement. This is not mean classes don’t have many functions or variables. but as a whole class, its ambition should be to do a single job. Let’s take an example. Car service is a place of various functions.

Example of Single Responsibility Principle

// CarService.class
public class CarService {
   double billAmount;
   double bay;
   public double processBill(){
      // Process the service amount
      return billAmount;
   }
   public void fuelFill(double amount){
      //Fill fuel to car
   }
   public void changeEngineOil(){
      // Change car Engine Oil
   }
}

You can see the example and that is not according to the SRP because if billing process change or fulling mechanism change class need to be modified. Other than that, if a new process arrives that is not good to add to the same CarService class. Then break the Single Responsibility Principle.

If I change the class according to principle then I have to create 3 separate classes for the service center as “FuelStation”, “CarBilling”, OilStation, and main CarService for operating the services. Let’s design the class by diagram first.

Car Service break according to Single Responsibility Principle
// Main class managing other services
public class CarService {
   CarBill carBill;
   FuelStation fuelStation;
   OilStation oilStation;
   public static void main(String[] args) {
      // Car Service do the function separatetly
   }   
}

// Doing only Filling Fuels works
class FuelStation{
   double amount;
   public void fuelFill(double amount){
      //Fill fuel to car
   }
}

// Doing only Billing works
class CarBill{
   double billAmount;
   public double processBill(){
      // Process the service amount
      return billAmount;
   }
}

// Doing only change oil works
class OilStation{
   public void changeEngineOil(){
      // Change car Engine Oil
   }
}

Now Single class for single work and once introduce new service just need to add a separate class to the system. This principle is not only for java classes in programming but also used by microservices to separate services by doing pointed works and in JPA (Java Persistence API) to separate relational data mapping and process single type of work on database and so on.

Open-Closed Principle (OCP)

Definition: Application should be OPEN for extension and CLOSE for modification

Open-Closed Principle means application classes and modules open to add or improve the code base without changing existing classes and modules. if modify the existing there should be new problems that will arise and need to reassure quality, retest all dependencies, and have to make sure all work fine.

But if it extends without modification then only need to verify newly added things working fine with existing modules. Let’s take an example as Mobile Info Class to clear this Open-Closed Principle.

Example of Open-Closed Principle

public class MobileInfo {
   public String GetMobileDetails(Object object){
      if (object instanceof  Samsung) {
        return ((Samsung) object).getInformation();
      }
      if (object instanceof  Apple) {
        return ((Apple) object).getInformation();
      }
// Here need to add new If condition with newly recived brand
//    if (object instanceof  NewBrand) {
//      return ((Samsung) object).getInformation();
//    }
      return null;
   }
}

class Samsung{
   public String getInformation(){
      return "Samsung Information";
   }
}

class Apple{
   public String getInformation(){
      return "Apple Information";
   }
}

According to the example if a new Brande arrives then Mobileinfo class should add another If statement to cover the new brand information.

Mobile Info is according to the Open-Closed Principle

Now Problem solves according to the Open-Closed Principle. In this case, classes break into AppleInfo, SamsungInfo classes, and Mobileinfo interface. Once another brand arrives then create another class by implementing Mobileinfo.

interface Mobileinfo {
   public String getInformation();
}

// Samsung implement the Mobile
class Samsung implements Mobileinfo {
   public String getInformation(){
      return "Samsung Information";
   }
}

// Apple implement the Mobile
class Apple implements Mobileinfo {
   public String getInformation(){
      return "Apple Information";
   }
}

// NewBrand implement the Mobile
class NewBrand implements Mobileinfo {
   public String getInformation(){
      return "NewBrand Information";
   }
}

public class Mobile { 
   public static void main(String[] args) {
      MobileInfo info = new MobileInfo();
      // Objects are create using Mobileinfo(Parent) Reference
      Mobileinfo apple=new Apple();
      Mobileinfo samsung=new Samsung();
      // Passing Objects
      info.getMobileDetails(apple);
      info.getMobileDetails(samsung);
   }
   // This method accept all sub type of Mobileinfoes
   public String getMobileDetails(Mobileinfo mobileinfo){
      return mobileinfo.getInformation();
   }   
}

In this case, there should not worry about existing but just verify newly added classes to the system.  As we learn from above this is not only for the classes but principle valid with modules, components, and microservices as well.

Liskov Substitution Principle (LSP)

Definition: if S is a subtype of T, then objects of type T are able to replace with objects of type S without breaking the application.

Let’s simplify the Liskov Substitution Principle step by step. Inheritance is a well-known concept in Object-oriented programming (OOP) used to make Subclasses and Subclasses derived from the Parent class. Parent class has behaviors (Methods and function) and after inheritance, those behaviors are available in sub-classes as well.

Keep above in mind let’s move again into Liskov Substitution Principle. Principle saying There is class T with behavior m(). Objects from Type T are available to execute m() behavior correctly.

Then create class S by inheriting T. Now m() is available in S class as well.  According to the principal child should be able to do what the parent class does. Now Objects type of T replace with the Object type of S then the application should run as it is. This means Objects from Type S able to execute m() behavior without a problem.

Example of Liskov Substitution Principle

Let’s take a typical Bird example to clarify this further. create class Bird with a fly method. And inherit parrot from Bird. Then the parrot is able to fly and if Bird replaces it with a parrot there should not arise any problems. Let’s go through a BAD design of work first.

class Bird{
   public void fly(){
   // Birds Flying
   }
   public void eat(){
   // Birds Eating
   }
}
class Parrot extends Bird{
 // Fly and eat available in Parrot throught the Inheritance
}

class Ostrich extends Bird{
 // Fly, eat available in Ostrich throught the Inheritance. BAD Design
}
Ostrich is a Bird But Bird can’t replace with Ostrich.

But in this example, Ostrich is a Bird and Ostrich inherits the Bird. Now Bird can’t replace with Ostrich because Ostrich could not able to fly. This is breaking the Liskov Substitution Principle and principle saying able manage those types of situations when programming. Let’s take this example in a well manageable way.

class Bird{
   public void eat(){
   // Birds Eating
   }
}

// FlyingBird able to eat and fly
class FlyingBird extends Bird{
   public void fly(){
   // Birds Flying
   }
}

// Parrot able to eat(Bird can do) and fly (FlyingBird can do)
class Parrot extends FlyingBird{
 // Fly and eat available in Parrot throught the Inheritance
}

// Parrot able to eat(Bird can do) 
class Ostrich extends Bird{
 // Only eat available in Ostrich throught the Inheritance. Good Design
}

Now design satisfied with Liskov Substitution Principle and Ostrich can replace the Bird can do and Parrot able to replace both Bird and Flybird can do.

Interface Segregation Principle (ISP)

Definition 1: Interfaces should be split into smaller and more specific pieces.

Definition 2: Clients should not be forced to implement methods that are not useful to them.

Using the definition of principle it’s easy to understand the Interface Segregation Principle. Assume Interface with many methods and some are outdated or irrelevant to clients. But if Interface extends by class then it forces us to implement every method in Interface.

This is breaking the Interface Segregation Principle because of outdated or irrelevant methods not required to users. Therefore principle suggests brake interface into smaller and specific pieces. Then users allow to inherit and implement what they required.

Example of Interface Segregation Principle (ISP)

Let’s take an example of Medical Treatment types in hospitals. If we create an interface with methods like useTablets, Vaccinate, Operation, and so on in the Treatment interface. Then patients need to get and implement all the treatments there self but every treatment type is not required with patients.

interface MedicalTreatmentType {
   void useTablets();
   void vaccinate();
   void operation();
}

class Patient implements MedicalTreatmentType{
   public void useTablets(){
      // Impletemnt useTablets
   }
   public void vaccinate(){
       // Impletemnt vaccinate
   }
   public void operation(){
       // Impletemnt operation
   }
}

Therefore here brake Treatment type interface into small and specific pieces. Then Patients are easy to get and use what they want. Let’s create separate interfaces such as Vaccinate, Operation, Tablet. This example demonstrates the usability of the Interface Segregation Principle.

interface Tablets {
   void useTablets();
}

interface Vaccinate {
   void useVaccinate();
}

interface Operation {
   void useOperation();
}

// Patient feel free to implements what Patient required
class Patient implements Operation{   
   public void useOperation(){
       // Only Impletemnt useOperation
   }
}

Dependency Inversion Principle (DIP)

Definition 1: Abstractions should not depend on concrete. Concrete should depend on abstractions

Definition 2: High-level modules should not depend on the low-level module, but both should depend on the abstraction (Interfaces or Abstract).

The Dependency Inversion Principle is mainly focusing on decoupling the application between modules, packages, or classes. According to the definition’s relationship between though the classes via Interface or Abstract class. This concept uses in the spring framework for Auto wiring.

Example of Dependency Inversion Principle (DIP)

Let’s take a car as an example. There are cars with petrol engines and diesel engines. Those cars might have Auto gear and others may be manual gear.  If the car directly connects with petrol engine and auto gear concrete classes, then its breaks the Dependency Inversion Principle.

// MoterCar directly connect with AutoGear and PetrolEngine
class MoterCar{
   private AutoGear autoGear;
   private PetrolEngine petrolEngine;
   public MoterCar(AutoGear autoGear, PetrolEngine petrolEngine) {
      this.autoGear = autoGear;
      this.petrolEngine = petrolEngine;
   }
   public void testCar(){
      petrolEngine.start();
      autoGear.changeGear();   
   } 
}

Therefore principle suggests connecting with the engine Interface and the Gear Interface. Let’s try to understand it using code and the diagram. According to the source now the car connects with interfaces also AutoGear, ManuelGear, PetrolEngine, and DieselEngine concrete classes are connected to an interface.

Moter Car Design according to Dependency Inversion Principle

Redesign the example according to the concept of “High-level modules should not depend on the low-level module, but both should depend on the abstraction”. Let’s implement the design in a more readable way.

interface GearBox{
   void changeGear();
}

interface Engine{
   void start();
}

class AutoGear implements GearBox{
   public void changeGear(){
      System.out.println("Auto Gear change");
   }
}

class ManuelGear implements GearBox{
   public void changeGear(){
      System.out.println("Manuel Gear change");
   }
}

class PetrolEngine implements Engine{
   public void start(){
      System.out.println("Petrol Engine start");
   }
}

class DieselEngine implements Engine{
   public void start(){
      System.out.println("Diesel Engine start");
   }
}

class MoterCar{
   private GearBox gearBox;
   private Engine engine;
   public MoterCar(GearBox gearBox, Engine engine) {
      this.gearBox = gearBox;
      this.engine = engine;
   }
   public void testCar(){
      engine.start();
      gearBox.changeGear();   
   } 
}

public class DependancyInversion {
      public static void main(String[] args) {
         GearBox gearBox=new AutoGear();
         Engine engine=new PetrolEngine();
         // Dynamically bind the object and run
         new MoterCar(gearBox, engine).testCar();
      }
}

Advantages of SOLID Principles

There are so many advantages when following the SOLID principles. But here I mention top level advantages

  • System is testable, maintainable, and reusable.
  • Principles intention is to reduce the dependencies there for easy to extend and extended part does not affect to hole.
  • Easy to understand and easy to design.

Summary

Programming is just not the coding but there are lots of things to consider. For that SOLID principles are one of the good solutions to follow. Keep in mind all 5 principles as a whole. Because we use one in a good way another one can be broken. Make sure to use those when designing and implementing. It will surely make you a smart programmer.

Share on facebook
Share on twitter
Share on linkedin
Share on email
Share on whatsapp
Share on pinterest
Share on print

Leave a Comment

Your email address will not be published. Required fields are marked *

Related Articles
You May Like
Subscribe to our Newsletter
Scroll to Top