An Overview Of SOLID Principles

Quality Coding Practice 

Before design patterns there are some principles followed by conventional programmers.This series of blogs will guide you if you don't know about it or if you are aware of it, then this is just to brush up on these best practices.

I strived to do it as narrative based to avoid rather boring theoretic examples. I think you will enjoy it while you are gaining knowledge by reading those stories. All examples here are my own examples in order to be very simple to understand even for the novice developer.

SOLID Principle

S.O.L.I.D. abbreviation means,

S -> Single Responsibility,

O -> Open Close principle

L -> Liskov Substitution principle

I -> Interface segregation principle

D -> Dependency inversion principle.

Let's discuss each concept in detail.

Single Responsibility Principle (SRP)  

Single Responsibility principle(SRP)

This is the most basic principle among these five principles. As the name implies this means one class does only one responsibility (in other words,  class has one reason to change). If many responsibilities are executed by that class just delegate that to other classes.

The advantage of it is that  you can understand code very cleanly and then you can reuse methods among the applications.

If you Google SRP then you will come up with a lot of sources, but it all seems to be a bit harder to understand for novices since this theory most of the time is used with enterprise level complex applications (great architecture design perspective).

I think before that first of all we have to thoroughly understand the principle and based on that we can add more paradigms of that principle in applications.

I am striving to give you a basic example of it and to try to elaborate to make it much understandable. I have cornered the example only to highlight the main concept.

OK here we go ..

It's  better to understand it  with SRP and without SRP.

I have a basic console calculator :)

(I am highlighting again, the following code is very basic to understand the SRP)

  1. class Program {  
  2.     static void Main(string[] args) {  
  3.         try {  
  4.             Console.WriteLine("Enter No 1 :");  
  5.             var no1 = Console.ReadLine();  
  6.             Console.WriteLine("Enter No 2 :");  
  7.             var no2 = Console.ReadLine();  
  8.             Console.WriteLine("Enter operator :");  
  9.             var operation = Console.ReadLine();  
  10.             switch (operation) {  
  11.                 case "+":  
  12.                     Console.WriteLine("your result is {0}:", Convert.ToDouble(no1) + Convert.ToDouble(no2));  
  13.                     break;  
  14.                 case "-":  
  15.                     Console.WriteLine("your result is {0}:", Convert.ToDouble(no1) - Convert.ToDouble(no2));  
  16.                     break;  
  17.                 default:  
  18.                     break;  
  19.             }  
  20.             Console.ReadKey();  
  21.         } catch (Exception) {  
  22.             //error log    
  23.             throw;  
  24.         }  
  25.     }  
  26. }  

Wow nice... but if you show it to a professional developer he will turn this code upside down.

He ill say: "you have put lot of responsibilities in one class ... dude it's hard to maintain ..bla bla ..".

Oops what is  it...?

And this is how it should have tbeen written without breaking SRP.

"You seem to be implementing a calculator eh?? So why can't you create a class for it and make it that responsibility rather than put all responsibilities to program class and ask program class to fulfill parameters and show them the result?"

"ah ..then if you have a problem with calculator then the only one reason is to change the calculator rather than the whole class in Program ..."

  1. class Program {  
  2.     static void Main(string[] args) {  
  3.         Console.WriteLine("Enter No 1 :");  
  4.         var no1 = Console.ReadLine();  
  5.         Console.WriteLine("Enter No 2 :");  
  6.         var no2 = Console.ReadLine();  
  7.         Console.WriteLine("Enter operator :");  
  8.         var operation = Console.ReadLine();  
  9.         Console.WriteLine("Result:{0}", Calculator.MathOperation(no1, no2, operation));  
  10.         Console.ReadKey();  
  11.     }  
  12. }  
  13. public static class Calculator {  
  14.     public static double MathOperation(string no1, string no2, string operatorMark) {  
  15.         try {  
  16.             double result = 0;  
  17.             switch (operatorMark) {  
  18.                 case "+":  
  19.                     result = Convert.ToDouble(no1) + Convert.ToDouble(no2);  
  20.                     break;  
  21.                 case "-":  
  22.                     result = Convert.ToDouble(no1) - Convert.ToDouble(no2);  
  23.                     break;  
  24.                 default:  
  25.                     break;  
  26.             }  
  27.             return result;  
  28.         } catch (Exception) {  
  29.             throw;  
  30.         }  
  31.     }  
  32. }  

Now our Calculator class will do all calculation related things, not like in previous Program class. If you want to change the calculation logic do it in Calculator class only not the Program class, do one class and do a single responsibility and do it well.This is basically about SRP.

In practically if you wish to add error logging to the calculator then you have to make new class(ExceptionLoggerClass) to it and from the catch statement you call it, that also makes it the SRP way.

Open Close Principle (OCP)  

Open Close abbreviation mean open to extend and close for modification.

It is more vital to understand the concept behind this for more maintainable code designing.

It is quite hard to understand the concept without an example so let start the journey with examples,

suppose we need to create a small calculator(special one) witch calculates cubic volume.

  1. using System;  
  2. using System.Collections.Generic;  
  3. using System.Linq;  
  4. using System.Text;  
  5. using System.Threading.Tasks;  
  6. namespace Volum_OpenClose {  
  7.     class Program {  
  8.         static void Main(string[] args) {  
  9.             double oneSideLenght;  
  10.             Console.WriteLine("Enter one side lenght :");  
  11.             oneSideLenght = Convert.ToDouble(Console.ReadLine());  
  12.             double volum = CalculateVolum(oneSideLenght);  
  13.             Console.WriteLine("The volume is : {0}", volum.ToString());  
  14.             Console.ReadKey();  
  15.         }  
  16.         public static double CalculateVolum(double oneSideLength) {  
  17.             return oneSideLength * oneSideLength * oneSideLength;  
  18.         }  
  19.     }  
  20. }  

Wow nice aah....

Once I sell my calculator to the client then he demands: "Hey, Dude I only bought your calculator so it could calculate the volume of the cylinder." 

Hummm OK, I will change my code

  1. using System;  
  2. using System.Collections.Generic;  
  3. using System.Linq;  
  4. using System.Text;  
  5. using System.Threading.Tasks;  
  6. namespace Volum_OpenClose {  
  7.     class Program {  
  8.         static void Main(string[] args) {  
  9.             string ShapeType;  
  10.             Console.WriteLine("Enter type :");  
  11.             ShapeType = Console.ReadLine();  
  12.             dynamic operand = null;  
  13.             if (ShapeType == "cubic") {  
  14.                 Console.WriteLine("Enter one side lenght :");  
  15.                 double oneSideLenght = Convert.ToDouble(Console.ReadLine());  
  16.                 operand = new Tuple < string, double > (ShapeType, oneSideLenght);  
  17.             } else if (ShapeType == "cylinder") {  
  18.                 Console.WriteLine("Enter radius :");  
  19.                 double radius = Convert.ToDouble(Console.ReadLine());  
  20.                 Console.WriteLine("Enter height :");  
  21.                 double hieght = Convert.ToDouble(Console.ReadLine());  
  22.                 operand = new Tuple < string, doubledouble > (ShapeType, radius, hieght);  
  23.             }  
  24.             double volum = CalculateVolum(operand);  
  25.             Console.WriteLine("The volume is : {0}", volum.ToString());  
  26.             Console.ReadKey();  
  27.         }  
  28.         public static double CalculateVolum(dynamic operandValue) {  
  29.             double volume = 0;  
  30.             string shapeType = operandValue.Item1;  
  31.             if (shapeType == "cubic") {  
  32.                 double oneSide = Convert.ToDouble(operandValue.Item2);  
  33.                 volume = oneSide * oneSide * oneSide;  
  34.             } else if (shapeType == "cylinder") {  
  35.                 double radius = Convert.ToDouble(operandValue.Item2);  
  36.                 double height = Convert.ToDouble(operandValue.Item3);  
  37.                 volume = height * Math.PI * radius * radius;  
  38.             }  
  39.             return volume;  
  40.         }  
  41.     }  
  42. }  

Ok done :D ...

before I have to sell it to the customer I ask my lead to review my code ...

Lead: "Oh no what have you written..?"

Me: :( :( :( feeling sad ...."why what's wrong with this ???"

Lead :"You need to change a whole lot of code once again.The customer has asked for another shape don't you know the Open Close principle?? Don't you know that you have changed the existing behavior of the code ?"

Me :"What is it? How can I write it for any shape of calculation without changing the code? I wonder how it works?"

Lead:"Yes you are correct we do need to change it, there is no magic, but we have to do the changes with minimum breaks of existing code and a maximum way to extend it for future potential occurrences."

Here it's obvious we must support different shapes, let's do the code refactoring

  1. using System;  
  2. using System.Collections.Generic;  
  3. using System.Linq;  
  4. using System.Text;  
  5. using System.Threading.Tasks;  
  6. namespace Volum_OpenClose {  
  7.     class Program {  
  8.         static void Main(string[] args) {  
  9.             var shape = new object() as Shape;  
  10.             var objectType = string.Empty;  
  11.             Console.WriteLine("Please enter type of object");  
  12.             objectType = Console.ReadLine();  
  13.             if (objectType == "Cube") {  
  14.                 shape = new Cube();  
  15.             }  
  16.             if (objectType == "Cylinder") {  
  17.                 shape = new Cylinder();  
  18.             }  
  19.             InitializeParameters(shape);  
  20.             PrintCalculation(shape);  
  21.             //calculate Circumference ..etc    
  22.             Console.ReadKey();  
  23.         }  
  24.         private static void PrintCalculation(Shape shape) {  
  25.             Console.WriteLine("Volume of your shape is : {0 }", shape.calculateVolume());  
  26.         }  
  27.         public static void InitializeParameters(Shape generalShape) {  
  28.             generalShape.InitializeParameters();  
  29.         }  
  30.     }  
  31.     public abstract class Shape {  
  32.         public dynamic Operand;  
  33.         public string Operation {  
  34.             get;  
  35.             set;  
  36.         }  
  37.         public abstract void InitializeParameters();  
  38.         public abstract double calculateVolume();  
  39.     }  
  40.     public class Cube: Shape {  
  41.         public override double calculateVolume() {  
  42.             return Operand * Operand * Operand;  
  43.         }  
  44.         public override void InitializeParameters() {  
  45.             Console.WriteLine("Please enter one side lenght :");  
  46.             Operand = Convert.ToDouble(Console.ReadLine());  
  47.         }  
  48.     }  
  49.     public class Cylinder: Shape {  
  50.         public override double calculateVolume() {  
  51.             return Operand[1] * Math.PI * Operand[0] * Operand[0];  
  52.         }  
  53.         public override void InitializeParameters() {  
  54.             Operand = new double[2];  
  55.             Console.WriteLine("Please enter radius:");  
  56.             Operand[0] = Convert.ToDouble(Console.ReadLine());  
  57.             Console.WriteLine("Please enter height:");  
  58.             Operand[1] = Convert.ToDouble(Console.ReadLine());  
  59.         }  
  60.     }  
  61. }  

Now you see there is no need to change the existing code each time you introduce new shapes; just add that class and only creation logic changes.  Others remain the same so there is no need to retest your code, just test the newly introduced separate classes."

The next time the client asks for different calculations see how easy it is ....:D:D

So everyone is happy.

Liskov substitution Principle (LSP) 

Let's dive into the next pillar of solid design principles.

Linkov substitution principle is a very high level state in which the pattern that derived class uses should be substituted by it' base class.

If you have googled the LSP, you will come up with square and rectangle examples. 

I'm fed up with this conventional example aren't you??

Let's talk about it in a simple, out-of-the-box way. Suppose I have a simple class called "SingleClass" and I implement it like this,

  1. public class SingleClass {  
  2.     int _Value1;  
  3.     int _Value2;  
  4.     public virtual void setValue1(int a) {  
  5.         _Value1 = a;  
  6.     }  
  7.     public virtual int getValue1() {  
  8.         return _Value1;  
  9.     }  
  10.     public virtual void setValue2(int a) {  
  11.         _Value2 = a;  
  12.     }  
  13.     public virtual int getValue2() {  
  14.         return _Value2;  
  15.     }  
  16.     public virtual int Sum() {  
  17.         return _Value1 + _Value2;  
  18.     }  
  19. }  
  20. cool..even kid can understand is 't it ?    
  21. I have call to my derived class as follows,  
  22. public class LiskovDoubleClass: SingleClass {  
  23.     public override void setValue1(int a) {  
  24.         base.setValue2(2 * a);  
  25.     }  
  26.     public override void setValue2(int a) {  
  27.         base.setValue2(2 * a);  
  28.     }  
  29.     public override int Sum() {  
  30.         return (base.getValue1() + base.getValue2());  
  31.     }  
  32. }  

In my main method(clients) utilize LiskovDoubleClass as follows 

  1. LiskovDoubleClass doubleClassRefactored = new LiskovDoubleClass();  
  2. doubleClassRefactored.setValue1( 4);  
  3. doubleClassRefactored.setValue2(5);  
  4. var childConditionRefactored = doubleClassRefactored.getValue1() + doubleClassRefactored.getValue2() == doubleClassRefactored.Sum();  

Now according to LSK,the child class should have to be substituted by its base class without any trouble,

Ok I will substitute my LiskovDouble class with it base class called SingleClass 

  1. SingleClass singleClass = new SingleClass();  
  2. singleClass.setValue1(4);  
  3. singleClass.setValue2( 5);  

var parentCondition = singleClass.getValue1() + singleClass.getValue2() == singleClass.Sum();

WOW the base class successfully replaced with it derived class (just scope to client)

In order to understand it better think about the following scenario,

  1. public class DoubleClass: SingleClass {  
  2.     public virtual int Sum() {  
  3.         return 2 * (getValue1() + getValue2());  
  4.     }  
  5. }  

my consumer 

  1. DoubleClass doubleClass = new DoubleClass();  
  2. doubleClass.setValue1(4);  
  3. doubleClass.setValue2(5);  
  4. var childCondition = 2*(doubleClass.getValue1() + doubleClass.getValue2()) == doubleClass.Sum();  

replace with base, 

  1. SingleClass singleClass = new SingleClass();  
  2. singleClass.setValue1(4);  
  3. singleClass.setValue2( 5);  
  4.   
  5. var parentCondition = 2*(singleClass.getValue1() + singleClass.getValue2()) == singleClass.Sum();  

now break the LSP. 

Interface segregation principle (ISP)  

Ok lets talk about a very very important  concept called "Interface segregation principle"

As the name implies, interface segregation means interface should not be a fat big interface, it should have to be segregated into thin interfaces where we can decouple greatly.

Suppose we have calculator functionalities as follows,

  1. public interface ICalculatorOperation    
  2. {    
  3.    double Add(double a,double b);    
  4.    double Substract(double a, double b);    
  5.    double Multiply(double a, double b);    
  6.    double Divide(double a, double b);    
  7. }    

Then my calculator is implemented as follows,

  1. public class Calculator: ICalculatorOperation {  
  2.     public double Add(double a, double b) {  
  3.         throw new NotImplementedException();  
  4.     }  
  5.     public double Divide(double a, double b) {  
  6.         throw new NotImplementedException();  
  7.     }  
  8.     public double Multiply(double a, double b) {  
  9.         throw new NotImplementedException();  
  10.     }  
  11.     public double Substract(double a, double b) {  
  12.         throw new NotImplementedException();  
  13.     }  
  14. }  

Suppose I am selling this calculator to the clients ..WOW great business is't it ?

Now I'm going to market this software to a client who is a measuring guy .

He says he will buy my calculator if it has the  functionality of calculating the area of a circle.

(I suggest you can calculate this by  pi R in to R )

Then that guy is not satisfied with my answer.

OK I come to my development team and tell the story ...

I pressure the development team to do it ASSP :( :(

Then because of high pressure from me, after all they have implemented an area calculation included with my previous calculator as follows,

  1. public interface ICalculatorOperation {  
  2.     double Add(double a, double b);  
  3.     double Substract(double a, double b);  
  4.     double Multiply(double a, double b);  
  5.     double Divide(double a, double b);  
  6.     double AreaOfCircle(double r);  
  7. }  
  8. public class Calculator: ICalculatorOperation {  
  9.         public double Add(double a, double b) {  
  10.             throw new NotImplementedException();  
  11.         }  
  12.         public double AreaOfCircle(double r) {  
  13.             throw new NotImplementedException();  
  14.         }  
  15.         public double Divide(double a, double b) {  
  16.             throw new NotImplementedException();  
  17.         }  
  18.         public double Multiply(double a, double b) {  
  19.             throw new NotImplementedException();  
  20.         }  
  21.         public double Substract(double a, double b) {  
  22.             throw new NotImplementedException();  
  23.         }  

I show it to my area measuring guy to sell the sofware and he is happy with the new feature.

But ....

when my conventional client comes to buy my software I ask for money for my new feature which they do not want to pay. I say you  should have to buy that one too I can't give it you a free new feature ... :(:(.

Not only that, because of adding a new feature some problem occurred and then my previous client also complains that I broke the system.

So after figuring out that something went wrong with the software design, I hire a software consultant....

Then they drill down the problem and give me the solution as follows.

  1. public interface ICalculatorOperation {  
  2.     double Add(double a, double b);  
  3.     double Substract(double a, double b);  
  4.     double Multiply(double a, double b);  
  5.     double Divide(double a, double b);  
  6.     double AreaOfCircle(double r);  
  7. }  

This interface has tightly coupled the components and we need to  segregate them

  1. public interface IArithmaticCalculator {  
  2.     double Add(double a, double b);  
  3.     double Substract(double a, double b);  
  4.     double Multiply(double a, double b);  
  5.     double Divide(double a, double b);  
  6. }  
  7. public interface IAreaCalculator {  
  8.     double AreaOfCircle(double r);  
  9. }  
  10. public class AreaCalculator: IAreaCalculator {  
  11.     public double AreaOfCircle(double r) {  
  12.         throw new NotImplementedException();  
  13.     }  
  14. }  
  15. public class ArithmaticCalculator: IArithmaticCalculator {  
  16.     public double Add(double a, double b) {  
  17.         throw new NotImplementedException();  
  18.     }  
  19.     public double Divide(double a, double b) {  
  20.         throw new NotImplementedException();  
  21.     }  
  22.     public double Multiply(double a, double b) {  
  23.         throw new NotImplementedException();  
  24.     }  
  25.     public double Substract(double a, double b) {  
  26.         throw new NotImplementedException();  
  27.     }  
  28. }  
  29. public class FullCalculator: IArithmaticCalculator, IAreaCalculator {  
  30.     public double Add(double a, double b) {  
  31.         throw new NotImplementedException();  
  32.     }  
  33.     public double AreaOfCircle(double r) {  
  34.         throw new NotImplementedException();  
  35.     }  
  36.     public double Divide(double a, double b) {  
  37.         throw new NotImplementedException();  
  38.     }  
  39.     public double Multiply(double a, double b) {  
  40.         throw new NotImplementedException();  
  41.     }  
  42.     public double Substract(double a, double b) {  
  43.         throw new NotImplementedException();  
  44.     }  
  45. }  

Wow. Now the problem is solved ..

Dependency Inversion Principle (DIP)  

Theoretically Dependency Inversion Principle states that,

Higher level module should not depend upon lower level module. They should depend via abstraction.

Abstraction should not depend on details, details should depends on abstraction.

It's a little bit tricky isn't it ?

As we have seen in other principles,this principle's intention is also to decouple the classes and modules.

OK let's try to understand it with examples.

Suppose there are two companies which are giving hiring facilities to customers.

There is a hotel which consumes the service of the hiring facility.

Let's see it as follows though a simple implementation,

Some company gives a vehicle which has super facilities such as beds for resting.Here is the implementation of it.

  1. public class SuperLuxuryVehicle {  
  2.     private int NoOfBeds {  
  3.         get;  
  4.         set;  
  5.     }  
  6.     private int IntererVoltage {  
  7.         get;  
  8.         set;  
  9.     }  
  10.     public double RentalPerDay() {  
  11.         // based on the properties calculation done for daily rental fee.    
  12.         throw new NotImplementedException();  
  13.     }  
  14. }  

Suppose some other small car rents their vehicles where HotelXYZ gets hired.

  1. public class BudgetVehicle {  
  2.     private int NoOfSeat {  
  3.         get;  
  4.         set;  
  5.     }  
  6.     private double Milage {  
  7.         get;  
  8.         set;  
  9.     }  
  10.     public double RentalPerDay() {  
  11.         // based on the properties calculation done for daily rental fee.    
  12.         throw new NotImplementedException();  
  13.     }  
  14. }  

The hotel uses the above two (consumer class) vehicles for their customers and calculates the bill according to car type.

  1. public class HotelXYZ {  
  2.     SuperLuxuryVehicle _slv;  
  3.     BudgetVehicle _bv;  
  4.     public HotelXYZ() {  
  5.         _slv = new SuperLuxuryVehicle();  
  6.         _bv = new BudgetVehicle();  
  7.     }  
  8.     public double generateClientBill() {  
  9.         // based on type generate bill    
  10.         throw new NotImplementedException();  
  11.     }  
  12. }  

As you can see here, their two hiring classes are tightly coupled with the HotelXYZ high level class.

(hint : If there are many "new" key words it indicates bad practices where tightly coupled code was introduced.)

If the hiring company (low level classes) changes the logic, it will directly effect the HotelXYZ high level class, so it is violating the DIP.

OK let's see how we can overcome it by introducing DIP.

  1. public interface IHireService {  
  2.     double RentalPerDay();  
  3. }  
  4. public class SuperLuxuryVehicle: IHireService {  
  5.     private int NoOfBeds {  
  6.         get;  
  7.         set;  
  8.     }  
  9.     private int IntererVoltage {  
  10.         get;  
  11.         set;  
  12.     }  
  13.     public double RentalPerDay() {  
  14.         // based on the properties calculation done for daily rental fee.    
  15.         throw new NotImplementedException();  
  16.     }  
  17. }  
  18. public class BudgetVehicle: IHireService {  
  19.     private int NoOfSeat {  
  20.         get;  
  21.         set;  
  22.     }  
  23.     private double Milage {  
  24.         get;  
  25.         set;  
  26.     }  
  27.     public double RentalPerDay() {  
  28.         // based on the properties calculation done for daily rental fee.    
  29.         throw new NotImplementedException();  
  30.     }  
  31. }  
  32. public class HotelXYZ {  
  33.     IHireService hireService;  
  34.     public HotelXYZ() {  
  35.         // initialized by dependency injection    
  36.     }  
  37.     public double generateClientBill() {  
  38.         hireService.RentalPerDay();  
  39.         hireService.RentalPerDay();  
  40.         // based on type generate bill    
  41.         throw new NotImplementedException();  
  42.     }  
  43. }  

Now both high level class and low level class interact with the abstraction, not the class definitions, which is the so called dependency inversion principle.

DRY

When we think about quality coding we must know the following practice too.(Don't get confused with SOLID where my topic is about quality coding.)

Ooops what is DRY ??

Many of you may already be aware what dry in coding is, if you are not, this is for you to merge with with the other codes.

The DRY abbreviation mean Do not Repeat Yourself.

As the name implies this simply means your code must not repeat the same logic many times.

Then you might think why it is so important as a principle ?

If you're writing a simple code in a project then it is trivial to come as principle.

But in enterprise level applications peoples are writing thousands of millions of lines of code in the project so in such a case if you are writing the same logic just copy and paste the code then think about if a newcomer wants to do some change of the behavior of some method, then that guy suffers the pain of it to find everywhere he needs to apply same change.

But if you only use that method in one place and other places you just reuse it, then you are minimizing the lines of coding with what you are writing (minimum coding means minimum unit test ..and minimum maintainability) then if that new comer comes to change the behavior of that method that guy needs not to worry about every occurrence of change just simply do the change in once place and be happy.

Other benefit of it is you seamlessly test your reusable code many times then that method is more robust.

Another thing is if you are changing a highly reused code then if you change such a method incorrectly then the impact of that change gives your application a massive blow.

Thanks for your valuable time for reading this article, and if you can give feedback on this I appreciate it. I hope the next time you code you stay dry ... :)

Up Next
    Ebook Download
    View all
    OOP/OOD
    Read by 0 people
    Download Now!
    Learn
    View all