SOLID Principle in depth with Practical Example
Categories - Laravel PHP Framework PHP Tags - Java PHP Laravel   Maniruzzaman Akash   3 years ago   4406   6 minutes   0

SOLID Principle in depth with Practical Example

SOLID Principle - A First Five Object Oriented Design (OOD) design pattern published by Robert C. Martin also known as Uncle Bob.

  • S - Single Responsibility Principle
  • O - Open-Close Principle
  • L - Liskov Substitution Principle
  • I - Interface Segregation Principle
  • D - Dependency Inversion Principle

 

S - Single Responsibility Principle

One Class will be responsible for only one task. That means, a class will not do two tasks simultaneously. 

 -- A class should have only one and one reason to be changed. A class should have only one job. 

 

Look at this Classes which breaks this Single responsibility principle.

Real-Life Example of Single Responsibility Priniciple - 

class Invoice{
  public float amount;
  public int customerID;
  public string customerName;
  public string customerEmail;

  public float sendEmailToCustomer();

  public float printInvoice();
}

An invoice class which do these things -

  1. Initiate some values like, amount, customerID and customerName.
  2. Send Email to Customer Email Address
  3. Print Invoice 

So, look at this a single class which is responsible for three tasks, 1) Initialize data, 2) Send Email, 3) Print Invoice. This just breaks the Single responsibility principle.

Rather we could do this to keep Single responsibility principle neet and clean.


class Invoice{
  public float amount;
  public int customerID;
  public string customerName;
  public string customerEmail;
}

clas SendInvoiceEmail extends Invoice{
  public void sendEmailToCustomer(){
    // Send Mail tasks
  }
}

clas PrintInvoice extends Invoice{
  public void printInvoice(){
    // Print Invoice tasks
  }
}

So, now look at the classes, we have now three classes and every class is responsible for only one job. So, it keeps the Single Responsibility Principle rule. It does not violates the rule now.

 Key points about Single Responsibilty Principle:

  1. A class should have only one and one reason to be changed.
  2. Only one job for a class and separate other functionality to other classes.
  3. Everything should have it's own place and specific.
  4. It's not always necessary to create multiple function for a job/class. Just be specific about the job/function. And do that in one class.

 

O - Open-Close Principle

A Class done in one time is Open for extends and Close for modification.

That means - We'll make a class fixed for doing something and if there needs any future changes, don't write code on the class rather than just extend it.

 

Real-Life Example of Open-Close Priniciple - 

Let's assume we have a Payment class. where the following stuffs are done.

class Payment{
  public string $paymentType;
  public function initiatePayments();

  public function getPayment(){
     if($this->paymentType === "paypal"){
        // Do Paypal type works
     }else if($this->paymentType === "Stripe"){
        // Do Stripetype works
     }
  }
}

 Let's add another method and if-else block will be added another. We can do that this way like -

class Payment{
  public function initiatePayments();

  public function getPaymentForPaypal(){
     // Do Paypal type works
  }


  public function getPaymentForStripe(){
     // Do Stripe type works
  }

}

But still, we've to add new class in here if any new methods arrive. No get the proper way to achieve this Open-Close Principle rule.

interface Payable{
  /** 
  * Get Payment For Any Method
  */
  public function getPayment();
}

class PaypalPayment implements Payable{
  public function getPayment(){
     // Do Works for Paypal
  }
}

class StripePayment implements Payable{
  public function getPayment(){
     // Do Works For Stripe
  }
}

Now, assume we've new WiredPayment class which implements Payable interface.

class WiredPayment implements Payable{
  public function getPayment(){
     // Do Works For Wired Transfer
  }
}

It's Super Simple and scalable, right. So, we don't need to put code in Payment class anymore. It's now only open for extend and closed for modification.

Key points about Open-Close Principle - 

  1. Class will be opened for extend but closed for modification
  2. Don't add functionality to existing code, just extend the code.
  3. Sperate the classes function, so they could be extend easily and nothings broke.

 

L - Liskov Substitution Principle

Design subclasses in a manner so that it would not be unstable in any cases. That means- If the child classe violates any functionality of parent classes, it will break the Liskov substitution rule.

 

Real-Life Example of Liskov Substitution - 

class Bird{
  public $weight; 
  public $height; 
  public function fly();
}

Now, we want to create a new class called Ostrich which will extend this Bird class.

class Ostrich extends Bird{
  public function fly(); // It Breaks.
}

 But this is not possible. Ostrich can not fly. It breaks the parent classes rule. So, we can not design a subclass like this. So, it breaks the Liskov Substitution rules of SOLID principle.

 Key points about Liskov Substitution Principle - 

  1. Child class should not break parent class in any situation.
  2. We have to design child clases in a manner so that it can not break parent class'es rule.

 

I - Interface Segregation Principle

According to Robert C. Martin, Client should not be forced depend upon interfaces that they can not use. If any interface extend other interface, it's necessary to check every functions would be implemented, rather than create/segregate new interface.

 

Real-Life Example of Interface Segregation Principle - 

interface Payment{
  public function initiatePayments();
  public function getPayment();
}

public class BankPayment implements Payment{
  public function initiatePayments(){
    // Do initialization
  }

  public function getPayment(){
    // Make Payments Stuff
  }
}

This is a scenerio, when Interface Segregation Principle does not break. This means - A BankPayment class will implement the two classes which is necessary here. That means - Clients are using the classes or interfaces which they needs. Two functions are required in BankPayment class.

Interface Segregation rule will be broken in the below cases - 

interface Payment{
  public function initiatePayments();
  public function getPayment();

  public function getLoanHistory();
  public function printLoanPayments();
}

public class LoanPayment implements Payment{
  public function initiatePayments(){
    // Do initialization
  }

  public function getPayment(){
    // Make Payments Stuff
  }
  
  // Ok for this class
  public function getLoanHistory(){
    
  }

  // Ok for this class
  public function printLoanPayments(){

  }
}


public class BankPayment implements Payment{
  public function initiatePayments(){
    // Do initialization
  }

  public function getPayment(){
    // Make Payments Stuff
  }

  // Not Necessary for this class but have to implement
  public function getLoanHistory(){
    
  }

  // Not Necessary this class but have to implement
  public function printLoanPayments(){

  }
}

Now in BankPayment which don't have the two functions - getLoanHistory, printLoanPayments necessary but have to implement. So thish should be avoided to keep neet and clean the Interface segregation principle.

 

  Key points about Interface Segregation Principle - 

  1. Class should not use any interface, where there is extra unncessary functions for that class.
  2. Rather modifying the interface, segregate the interface's functionality and create new one.

 

D - Dependency Inversion Principle

Based on Uncle Bob - Higher level class will not depend on lower level class and lower level class also will not depend on it's class, rather both will depends on it's abstruction.

That means - let's assume, we've some business level class and some initialization or very lower level class. So, Business level class or higher level class will not depend on initialization class. Or initialization class will also will not depend on himself. Rather we'll create a new class or interface which will be responsible for this.

Real-Life Example of Dependency Inversion Principle - 

class MySQLConnection{
  public string $DB;
  public function connect(){
    // Hnandle logic
  }
}

class Employee extends MySQLConnection{

   public string employeeInformation(){
       $db = new MySQLConnection();
       $db->connect();
       // Do business level stuffs like retreiving employee information
   }

}

class EmployeeSalary extends Employee{
   public float employeeSalary(){
       $db = new MySQLConnection();
       $db->connect();
       // Do business level stuffs like retreiving employee salary
   }

}

Look at this example here - we have a class called DBConnection which will connect throw the database first and then execute the business levels code.

So, look at this - we've to depend on this database connection class or a low level class. to incorpate any staffs. So, if something changes in database then low level connection class will be infected. This breaks the dependency inversion principle (DIP).

 

So, how can we maintain this. 

Create an abstruction of this DBConnection class as an interface. and rather depends on DBConnection class, depends on DBConnection interface like this.

// Abstruction Class
interface DBConnectionInterface{
  public function connect(){}
}

class MySQLConnection implement DBConnectionInterface{
  public function connect(){
    // Establish connection through MySQL
  }
}

class Employee{
   public $db;

   public string employeeInformation(DBConnectionInterface $dBInterface){
       $this->db = $dBInterface;

       // Do business level stuffs like retreiving employee information
   }

}

class EmployeeSalary extends Employee{
   public $db;

   public string employeeInformation(DBConnectionInterface $dBInterface){
       $this->db = $dBInterface;

       // Do business level stuffs like retreiving employee salary information
   }
}

 

So, now if we need to change the database engine, it's no need to depend on the low level MySQLConnection class. rather depend on it's abstruction. And so open-close priniciple is also not violated.

   Key points about Dependency Inversion Principle (DIP) - 

  1. High-level modules depend on the abstruction
  2. Low-level modules also depends on the abstruction

 

Conclusion about SOLID Principle

So, in here we've discussed in depth of SOLID principle of Object Oriented Programming. It's a very important concept in Object Oriented Programming.

 

 

Previous
PHP If-else-elseif and Switch-case
Next
PHP String Functions - All necessary String functions in PHP to manage strings better.